[INFRA] Import NVIDIA/CCCL upstream as optimization reference library

CCCL (CUDA C++ Core Libraries) provides:
- CUB: device/block/warp-level GPU primitives (reduce, scan, sort, topk)
- Thrust: high-level parallel algorithms (transform_reduce, sort, scan)
- libcudacxx: CUDA C++ standard library (atomics, barriers, memory)
- cudax: experimental features (memory resources, allocators)
- Tuning policies: per-SM hardware-specific algorithm parameters

Competition optimization vectors mapped to CCCL:
- Output TPS (83% weight): warp_reduce, block_reduce, device_topk
- Input TPS (14% weight): device_scan, block_load, prefetch
- Cache TPS (3% weight): prefix caching strategy patterns
- Memory (0.9 util): pooled/cached/buddy allocators

Source: https://github.com/NVIDIA/cccl (shallow clone, HEAD only)
License: Apache-2.0
This commit is contained in:
EngineX CI
2026-07-30 09:35:51 +00:00
parent b4d01f481e
commit 56fd68e7dd
8871 changed files with 1454674 additions and 0 deletions

View File

@@ -0,0 +1,120 @@
# 3.30 is required for FindCUDAToolkit's CUDA::nvfatbin / CUDA::nvfatbin_static
# imported targets, which the HostJIT linker chain depends on.
cmake_minimum_required(VERSION 3.30)
project(CCCL_C_Parallel_V2 LANGUAGES CUDA CXX C)
# Bootstrap CCCL cmake helpers when building c/parallel.v2 in isolation
# (i.e. not as a subdirectory of the CCCL super-project).
if (NOT COMMAND cccl_configure_target)
# Repo root is two levels up from this file (c/parallel.v2 -> c -> cccl)
get_filename_component(
_cccl_root
"${CMAKE_CURRENT_SOURCE_DIR}/../.."
ABSOLUTE
)
set(CCCL_SOURCE_DIR "${_cccl_root}" CACHE PATH "CCCL repo root" FORCE)
set(
CCCL_BINARY_DIR
"${CMAKE_CURRENT_BINARY_DIR}"
CACHE PATH
"CCCL binary root"
FORCE
)
include("${_cccl_root}/cmake/CCCLUtilities.cmake")
include("${_cccl_root}/cmake/CCCLConfigureTarget.cmake")
include("${_cccl_root}/cmake/CCCLGetDependencies.cmake")
if (NOT TARGET cccl.compiler_interface)
add_library(cccl.compiler_interface INTERFACE)
endif()
endif()
option(CCCL_C_Parallel_V2_ENABLE_TESTING "Build cccl.c.parallel.v2 tests." OFF)
set(
CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY
""
CACHE PATH
"Override output directory for the cccl.c.parallel.v2 library"
)
mark_as_advanced(CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY)
file(
GLOB_RECURSE srcs
RELATIVE "${CMAKE_CURRENT_LIST_DIR}"
CONFIGURE_DEPENDS
"src/*.cu"
"src/*.cpp"
)
# hostjit sources are built as a separate library
list(FILTER srcs EXCLUDE REGEX "^src/hostjit/")
# Editor lock/temp files
list(FILTER srcs EXCLUDE REGEX "/\\.#")
add_library(cccl.c.parallel.v2 SHARED ${srcs})
set_property(TARGET cccl.c.parallel.v2 PROPERTY POSITION_INDEPENDENT_CODE ON)
cccl_configure_target(cccl.c.parallel.v2 DIALECT 20)
if (CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY)
set_target_properties(
cccl.c.parallel.v2
PROPERTIES
LIBRARY_OUTPUT_DIRECTORY "${CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY}"
ARCHIVE_OUTPUT_DIRECTORY "${CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY}"
RUNTIME_OUTPUT_DIRECTORY "${CCCL_C_PARALLEL_V2_LIBRARY_OUTPUT_DIRECTORY}"
)
endif()
cccl_get_cub()
cccl_get_cudatoolkit()
cccl_get_thrust()
add_subdirectory(src/hostjit)
set_target_properties(cccl.c.parallel.v2 PROPERTIES CUDA_RUNTIME_LIBRARY STATIC)
target_link_libraries(
cccl.c.parallel.v2
PRIVATE
cccl.compiler_interface
CUDA::cudart_static
CUDA::cuda_driver
CUB::CUB
Thrust::Thrust
cccl.c.parallel.v2.hostjit_lib # transitively brings in nvJitLink, nvfatbin, nvptxcompiler
)
if (WIN32)
target_link_libraries(cccl.c.parallel.v2 PRIVATE Dbghelp)
# We are shadowing a lot of variables with the globals like num_items
target_compile_options(
cccl.c.parallel.v2
PRIVATE $<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:-Xcompiler=/wd4459>
)
target_compile_definitions(
cccl.c.parallel.v2
PRIVATE CATCH_CONFIG_NO_WINDOWS_SEH
)
endif()
target_compile_definitions(
cccl.c.parallel.v2
PUBLIC CCCL_C_EXPERIMENTAL=1
PRIVATE #
NVRTC_GET_TYPE_NAME=1
CUB_DISABLE_CDP=1
CUB_DEFINE_RUNTIME_POLICIES
)
target_compile_options(
cccl.c.parallel.v2
PRIVATE $<$<COMPILE_LANG_AND_ID:CUDA,NVIDIA>:--extended-lambda>
)
target_include_directories(
cccl.c.parallel.v2 #
PUBLIC "include"
PRIVATE "src" "src/hostjit/include"
)
if (CCCL_C_Parallel_V2_ENABLE_TESTING)
add_subdirectory(test)
endif()

View File

@@ -0,0 +1,82 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_binary_search_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler; // hostjit::JITCompiler*
#if defined(_WIN32)
// Opaque state for serializing CUB's lazy first-call initialization.
void* first_call_state;
#endif // _WIN32
void* binary_search_fn; // int(*)(void*, ull, void*, ull, void*, void*, void*)
} cccl_device_binary_search_build_result_t;
CCCL_C_API CUresult cccl_device_binary_search_build(
cccl_device_binary_search_build_result_t* build,
cccl_binary_search_mode_t mode,
cccl_iterator_t d_data,
cccl_iterator_t d_values,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_binary_search_build_ex(
cccl_device_binary_search_build_result_t* build,
cccl_binary_search_mode_t mode,
cccl_iterator_t d_data,
cccl_iterator_t d_values,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_binary_search(
cccl_device_binary_search_build_result_t build,
cccl_iterator_t d_data,
uint64_t num_items,
cccl_iterator_t d_values,
uint64_t num_values,
cccl_iterator_t d_out,
cccl_op_t op,
CUstream stream);
CCCL_C_API CUresult cccl_device_binary_search_cleanup(cccl_device_binary_search_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,23 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#ifdef __cplusplus
# define CCCL_C_EXTERN_C_BEGIN extern "C" {
# define CCCL_C_EXTERN_C_END }
#else
# define CCCL_C_EXTERN_C_BEGIN
# define CCCL_C_EXTERN_C_END
#endif

View File

@@ -0,0 +1,65 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_for_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler; // hostjit::JITCompiler*
void* for_fn; // int(*)(void*, unsigned long long, void*)
} cccl_device_for_build_result_t;
CCCL_C_API CUresult cccl_device_for_build(
cccl_device_for_build_result_t* build,
cccl_iterator_t d_data,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_for_build_ex(
cccl_device_for_build_result_t* build,
cccl_iterator_t d_data,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_for(
cccl_device_for_build_result_t build, cccl_iterator_t d_data, uint64_t num_items, cccl_op_t op, CUstream stream);
CCCL_C_API CUresult cccl_device_for_cleanup(cccl_device_for_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,96 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdbool.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_histogram_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler;
void* histogram_fn;
cccl_type_info counter_type;
cccl_type_info level_type;
cccl_type_info sample_type;
int num_channels;
int num_active_channels;
} cccl_device_histogram_build_result_t;
CCCL_C_API CUresult cccl_device_histogram_build(
cccl_device_histogram_build_result_t* build,
int num_channels,
int num_active_channels,
cccl_iterator_t d_samples,
int num_output_levels_val,
cccl_iterator_t d_output_histograms,
cccl_type_info level_type,
int64_t num_rows,
int64_t row_stride_samples,
bool is_evenly_segmented,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_histogram_build_ex(
cccl_device_histogram_build_result_t* build,
int num_channels,
int num_active_channels,
cccl_iterator_t d_samples,
int num_output_levels_val,
cccl_iterator_t d_output_histograms,
cccl_type_info level_type,
int64_t num_rows,
int64_t row_stride_samples,
bool is_evenly_segmented,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_histogram_even(
cccl_device_histogram_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_samples,
cccl_iterator_t d_output_histograms,
cccl_value_t num_output_levels,
cccl_value_t lower_level,
cccl_value_t upper_level,
int64_t num_row_pixels,
int64_t num_rows,
int64_t row_stride_samples,
CUstream stream);
CCCL_C_API CUresult cccl_device_histogram_cleanup(cccl_device_histogram_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,86 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_merge_sort_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler;
void* sort_fn;
// 1 if the build compiled SortKeysCopy (no items), 0 if SortPairsCopy. The
// run function dispatches on this so the value-vs-pairs decision doesn't
// have to be re-derived from the iterator arguments.
int keys_only;
cccl_type_info key_type;
cccl_type_info item_type;
} cccl_device_merge_sort_build_result_t;
CCCL_C_API CUresult cccl_device_merge_sort_build(
cccl_device_merge_sort_build_result_t* build,
cccl_iterator_t d_in_keys,
cccl_iterator_t d_in_items,
cccl_iterator_t d_out_keys,
cccl_iterator_t d_out_items,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_merge_sort_build_ex(
cccl_device_merge_sort_build_result_t* build,
cccl_iterator_t d_in_keys,
cccl_iterator_t d_in_items,
cccl_iterator_t d_out_keys,
cccl_iterator_t d_out_items,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_merge_sort(
cccl_device_merge_sort_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in_keys,
cccl_iterator_t d_in_items,
cccl_iterator_t d_out_keys,
cccl_iterator_t d_out_items,
uint64_t num_items,
cccl_op_t op,
CUstream stream);
CCCL_C_API CUresult cccl_device_merge_sort_cleanup(cccl_device_merge_sort_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,90 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdbool.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_radix_sort_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler; /* Owns both wrappers below — one TU, one cubin */
void* sort_fn; /* Wrapper around CUB's copy-overload (selector always 0) */
void* sort_fn_overwrite; /* Wrapper around CUB's DoubleBuffer overload; reports selector */
cccl_type_info key_type;
cccl_type_info value_type;
cccl_sort_order_t order;
int keys_only; /* 1 if keys-only sort, 0 if key-value pairs */
} cccl_device_radix_sort_build_result_t;
CCCL_C_API CUresult cccl_device_radix_sort_build(
cccl_device_radix_sort_build_result_t* build,
cccl_sort_order_t sort_order,
cccl_iterator_t input_keys_it,
cccl_iterator_t input_values_it,
cccl_op_t decomposer,
const char* decomposer_return_type,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_radix_sort_build_ex(
cccl_device_radix_sort_build_result_t* build,
cccl_sort_order_t sort_order,
cccl_iterator_t input_keys_it,
cccl_iterator_t input_values_it,
cccl_op_t decomposer,
const char* decomposer_return_type,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_radix_sort(
cccl_device_radix_sort_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_in,
cccl_iterator_t d_values_out,
cccl_op_t decomposer,
uint64_t num_items,
int begin_bit,
int end_bit,
bool is_overwrite_okay,
int* selector,
CUstream stream);
CCCL_C_API CUresult cccl_device_radix_sort_cleanup(cccl_device_radix_sort_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,94 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_reduce_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler; // hostjit::JITCompiler*
void* reduce_fn; // int(*)(void*, size_t*, void*, void*, unsigned long long, void*, void*, void*) — trailing void* is
// the CUstream
uint64_t accumulator_size;
cccl_determinism_t determinism;
} cccl_device_reduce_build_result_t;
// TODO return a union of nvtx/cuda/nvrtc errors or a string?
CCCL_C_API CUresult cccl_device_reduce_build(
cccl_device_reduce_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
cccl_value_t init,
cccl_determinism_t determinism,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_reduce_build_ex(
cccl_device_reduce_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
cccl_value_t init,
cccl_determinism_t determinism,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_reduce(
cccl_device_reduce_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream);
CCCL_C_API CUresult cccl_device_reduce_nondeterministic(
cccl_device_reduce_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream);
CCCL_C_API CUresult cccl_device_reduce_cleanup(cccl_device_reduce_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,127 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdbool.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_scan_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler;
void* scan_fn;
bool force_inclusive;
cccl_init_kind_t init_kind;
} cccl_device_scan_build_result_t;
CCCL_C_API CUresult cccl_device_scan_build(
cccl_device_scan_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
cccl_type_info init,
bool force_inclusive,
cccl_init_kind_t init_kind,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_scan_build_ex(
cccl_device_scan_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
cccl_type_info init,
bool force_inclusive,
cccl_init_kind_t init_kind,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_exclusive_scan(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream);
CCCL_C_API CUresult cccl_device_inclusive_scan(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream);
CCCL_C_API CUresult cccl_device_exclusive_scan_future_value(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_iterator_t init,
CUstream stream);
CCCL_C_API CUresult cccl_device_inclusive_scan_future_value(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_iterator_t init,
CUstream stream);
CCCL_C_API CUresult cccl_device_inclusive_scan_no_init(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
CUstream stream);
CCCL_C_API CUresult cccl_device_scan_cleanup(cccl_device_scan_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,84 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_segmented_reduce_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler;
void* segmented_reduce_fn;
} cccl_device_segmented_reduce_build_result_t;
// TODO return a union of nvtx/cuda/nvrtc errors or a string?
CCCL_C_API CUresult cccl_device_segmented_reduce_build(
cccl_device_segmented_reduce_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
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);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_segmented_reduce_build_ex(
cccl_device_segmented_reduce_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
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,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_segmented_reduce(
cccl_device_segmented_reduce_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_segments,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
cccl_op_t op,
cccl_value_t init,
CUstream stream);
CCCL_C_API CUresult cccl_device_segmented_reduce_cleanup(cccl_device_segmented_reduce_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,91 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdbool.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_segmented_sort_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler; /* Owns both wrappers below — one TU, one cubin */
void* sort_fn; /* Wrapper around CUB's copy-overload (selector always 0) */
void* sort_fn_overwrite; /* Wrapper around CUB's DoubleBuffer overload; reports selector */
cccl_type_info key_type;
cccl_type_info value_type;
cccl_sort_order_t order;
int keys_only; /* 1 if keys-only sort, 0 if key-value pairs */
} cccl_device_segmented_sort_build_result_t;
// TODO return a union of nvtx/cuda/nvrtc errors or a string?
CCCL_C_API CUresult cccl_device_segmented_sort_build(
cccl_device_segmented_sort_build_result_t* build,
cccl_sort_order_t sort_order,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_segmented_sort_build_ex(
cccl_device_segmented_sort_build_result_t* build,
cccl_sort_order_t sort_order,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_segmented_sort(
cccl_device_segmented_sort_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_in,
cccl_iterator_t d_values_out,
uint64_t num_items,
uint64_t num_segments,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
bool is_overwrite_okay,
int* selector,
CUstream stream);
CCCL_C_API CUresult cccl_device_segmented_sort_cleanup(cccl_device_segmented_sort_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,87 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_three_way_partition_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler;
void* three_way_partition_fn;
} cccl_device_three_way_partition_build_result_t;
// TODO return a union of nvtx/cuda/nvrtc errors or a string?
CCCL_C_API CUresult cccl_device_three_way_partition_build(
cccl_device_three_way_partition_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_first_part_out,
cccl_iterator_t d_second_part_out,
cccl_iterator_t d_unselected_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t select_first_part_op,
cccl_op_t select_second_part_op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_three_way_partition_build_ex(
cccl_device_three_way_partition_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_first_part_out,
cccl_iterator_t d_second_part_out,
cccl_iterator_t d_unselected_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t select_first_part_op,
cccl_op_t select_second_part_op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_three_way_partition(
cccl_device_three_way_partition_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_first_part_out,
cccl_iterator_t d_second_part_out,
cccl_iterator_t d_unselected_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t select_first_part_op,
cccl_op_t select_second_part_op,
uint64_t num_items,
CUstream stream);
CCCL_C_API CUresult cccl_device_three_way_partition_cleanup(cccl_device_three_way_partition_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,113 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_transform_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler;
#if defined(_WIN32)
// Opaque state for serializing CUB's lazy first-call initialization.
void* first_call_state;
#endif // _WIN32
void* transform_fn;
} cccl_device_transform_build_result_t;
CCCL_C_API CUresult cccl_device_unary_transform_build(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_unary_transform_build_ex(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_unary_transform(
cccl_device_transform_build_result_t build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
CUstream stream);
CCCL_C_API CUresult cccl_device_binary_transform_build(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in1,
cccl_iterator_t d_in2,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_binary_transform_build_ex(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in1,
cccl_iterator_t d_in2,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_binary_transform(
cccl_device_transform_build_result_t build,
cccl_iterator_t d_in1,
cccl_iterator_t d_in2,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
CUstream stream);
CCCL_C_API CUresult cccl_device_transform_cleanup(cccl_device_transform_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,188 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#if defined(_WIN32)
# define CCCL_C_API __declspec(dllexport)
#else // ^^^ _WIN32 ^^^ / vvv !_WIN32 vvv
# define CCCL_C_API __attribute__((__visibility__("default")))
#endif // !_WIN32
#include <stddef.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
CCCL_C_EXTERN_C_BEGIN
typedef enum cccl_type_enum
{
CCCL_INT8 = 0,
CCCL_INT16 = 1,
CCCL_INT32 = 2,
CCCL_INT64 = 3,
CCCL_UINT8 = 4,
CCCL_UINT16 = 5,
CCCL_UINT32 = 6,
CCCL_UINT64 = 7,
CCCL_FLOAT16 = 8, // This may be unsupported if _CCCL_HAS_NVFP16() is false but we can't include the header to check
// that here
CCCL_FLOAT32 = 9,
CCCL_FLOAT64 = 10,
CCCL_STORAGE = 11,
CCCL_BOOLEAN = 12,
} cccl_type_enum;
typedef struct cccl_type_info
{
size_t size;
size_t alignment;
cccl_type_enum type;
} cccl_type_info;
typedef enum cccl_op_kind_t
{
// Arbitrary semantics, without state.
CCCL_STATELESS = 0,
// Arbitrary semantics, with state.
CCCL_STATEFUL = 1,
// Well-known semantics, required to be stateless.
// Equivalent to corresponding function objects in C++'s <functional>.
// If the types involved are primitive, only the kind field is necessary.
// Otherwise, the cccl_op_t object must also contain the rest of the fields,
// as appropriate.
CCCL_PLUS = 2,
CCCL_MINUS = 3,
CCCL_MULTIPLIES = 4,
CCCL_DIVIDES = 5,
CCCL_MODULUS = 6,
CCCL_EQUAL_TO = 7,
CCCL_NOT_EQUAL_TO = 8,
CCCL_GREATER = 9,
CCCL_LESS = 10,
CCCL_GREATER_EQUAL = 11,
CCCL_LESS_EQUAL = 12,
CCCL_LOGICAL_AND = 13,
CCCL_LOGICAL_OR = 14,
CCCL_LOGICAL_NOT = 15,
CCCL_BIT_AND = 16,
CCCL_BIT_OR = 17,
CCCL_BIT_XOR = 18,
CCCL_BIT_NOT = 19,
CCCL_IDENTITY = 20,
CCCL_NEGATE = 21,
CCCL_MINIMUM = 22,
CCCL_MAXIMUM = 23,
} cccl_op_kind_t;
typedef enum cccl_op_code_type
{
CCCL_OP_LTOIR = 0, // Pre-compiled LTO-IR (escape hatch for callers with existing nvcc -dlto artifacts).
// LTO-IR is a binary container passed to nvJitLink at the PTX level — the LLVM optimizer
// never sees it, so the operator cannot be inlined into the CUB kernel and pays a real
// CALL on every iteration. CCCL_OP_LLVM_IR feeds LLVM's bitcode linker instead, which
// merges the operator into the CUB module before PTX codegen and enables full inlining.
// Prefer CCCL_OP_LLVM_IR or CCCL_OP_CPP_SOURCE for any new code.
CCCL_OP_CPP_SOURCE = 1, // C++ source code (compiled to LLVM bitcode by hostjit's Clang).
CCCL_OP_LLVM_IR = 2 // LLVM bitcode (recommended) — merges into the CUB module before PTX gen, so inlines.
} cccl_op_code_type;
typedef struct cccl_op_t
{
cccl_op_kind_t type;
const char* name;
const char* code;
size_t code_size;
cccl_op_code_type code_type;
size_t size;
size_t alignment;
void* state;
const char** extra_ltoirs;
size_t* extra_ltoir_sizes;
size_t num_extra_ltoirs;
cccl_op_code_type* extra_code_types;
} cccl_op_t;
typedef struct cccl_build_config
{
const char** extra_compile_flags; // e.g., {"-DENABLE_FAST_MATH", "-O3"}
size_t num_extra_compile_flags;
const char** extra_include_dirs; // e.g., {"/path/to/my/headers"}
size_t num_extra_include_dirs;
int enable_pch; // Cache precompiled headers on disk to speed up repeated builds
int verbose; // Log PCH generation/usage and compiler args to build diagnostics
} cccl_build_config;
typedef enum cccl_iterator_kind_t
{
CCCL_POINTER = 0,
CCCL_ITERATOR = 1,
} cccl_iterator_kind_t;
typedef struct cccl_value_t
{
cccl_type_info type;
void* state;
} cccl_value_t;
typedef union
{
int64_t signed_offset;
uint64_t unsigned_offset;
} cccl_increment_t;
typedef void (*cccl_host_op_fn_ptr_t)(void*, cccl_increment_t);
typedef struct cccl_iterator_t
{
size_t size;
size_t alignment;
cccl_iterator_kind_t type;
cccl_op_t advance;
cccl_op_t dereference;
cccl_type_info value_type;
void* state;
cccl_host_op_fn_ptr_t host_advance;
} cccl_iterator_t;
typedef enum cccl_sort_order_t
{
CCCL_ASCENDING = 0,
CCCL_DESCENDING = 1,
} cccl_sort_order_t;
typedef enum cccl_init_kind_t
{
CCCL_VALUE_INIT = 0,
CCCL_FUTURE_VALUE_INIT = 1,
CCCL_NO_INIT = 2,
} cccl_init_kind_t;
typedef enum cccl_determinism_t
{
CCCL_NOT_GUARANTEED = 0,
CCCL_RUN_TO_RUN = 1,
CCCL_GPU_TO_GPU = 2,
} cccl_determinism_t;
typedef enum cccl_binary_search_mode_t
{
CCCL_BINARY_SEARCH_LOWER_BOUND = 0,
CCCL_BINARY_SEARCH_UPPER_BOUND = 1,
} cccl_binary_search_mode_t;
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,83 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
// NOLINTBEGIN(modernize-use-using)
#ifndef CCCL_C_EXPERIMENTAL
# error "C exposure is experimental and subject to change. Define CCCL_C_EXPERIMENTAL to acknowledge this notice."
#endif // !CCCL_C_EXPERIMENTAL
#include <cuda.h>
#include <stdint.h>
#include <cccl/c/extern_c.h>
#include <cccl/c/types.h>
CCCL_C_EXTERN_C_BEGIN
typedef struct cccl_device_unique_by_key_build_result_t
{
int cc;
void* payload;
size_t payload_size;
void* jit_compiler;
void* unique_by_key_fn;
} cccl_device_unique_by_key_build_result_t;
CCCL_C_API CUresult cccl_device_unique_by_key_build(
cccl_device_unique_by_key_build_result_t* build,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path);
// Extended version with build configuration
CCCL_C_API CUresult cccl_device_unique_by_key_build_ex(
cccl_device_unique_by_key_build_result_t* build,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config);
CCCL_C_API CUresult cccl_device_unique_by_key(
cccl_device_unique_by_key_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t op,
uint64_t num_items,
CUstream stream);
CCCL_C_API CUresult cccl_device_unique_by_key_cleanup(cccl_device_unique_by_key_build_result_t* bld_ptr);
CCCL_C_EXTERN_C_END
// NOLINTEND(modernize-use-using)

View File

@@ -0,0 +1,180 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cuda/std/version>
#include <cstdio>
#include <cstring>
#include <memory>
#include <cccl/c/binary_search.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
#include <util/first_call_gate.h>
using namespace hostjit::codegen;
// (d_in_0, num_items, d_in_1, num_values, d_out_0, op_0_state, stream)
using binary_search_fn_t = int (*)(void*, unsigned long long, void*, unsigned long long, void*, void*, void*);
CUresult cccl_device_binary_search_build_ex(
cccl_device_binary_search_build_result_t* build_ptr,
cccl_binary_search_mode_t mode,
cccl_iterator_t d_data,
cccl_iterator_t d_values,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
#if CCCL_OS(WINDOWS)
build_ptr->first_call_state = nullptr;
#endif
const std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
const std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* const cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* const ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
#if CCCL_OS(WINDOWS)
auto first_call_state = std::make_unique<cccl::detail::first_call_gate>();
#endif
const char* find_fn =
(mode == CCCL_BINARY_SEARCH_LOWER_BOUND) ? "cub::DeviceFind::LowerBound" : "cub::DeviceFind::UpperBound";
// env_stream uses the env-based DeviceFind overload so CUB manages its own
// temp storage via the env's memory_resource — no caller-managed buffer.
auto result =
CubCall::from("cub/device/device_find.cuh")
.run(find_fn)
.name("cccl_jit_binary_search")
.with(in(d_data), num_haystack, in(d_values), num_needles, out(d_out), cmp(op), env_stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
#if CCCL_OS(WINDOWS)
build_ptr->first_call_state = first_call_state.release();
#endif
build_ptr->binary_search_fn = result.fn_ptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_binary_search_build_ex(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_binary_search(
cccl_device_binary_search_build_result_t build,
cccl_iterator_t d_data,
uint64_t num_items,
cccl_iterator_t d_values,
uint64_t num_values,
cccl_iterator_t d_out,
cccl_op_t op,
CUstream stream)
try
{
if (!build.binary_search_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
const auto fn = reinterpret_cast<binary_search_fn_t>(build.binary_search_fn);
#if CCCL_OS(WINDOWS)
if (!build.first_call_state)
{
return CUDA_ERROR_INVALID_VALUE;
}
const auto invoke = [&] {
return fn(
d_data.state, num_items, d_values.state, num_values, d_out.state, op.state, reinterpret_cast<void*>(stream));
};
// Empty calls return before DeviceTransform initializes its static launch configuration,
// so they must not complete the first-call gate.
const int status =
num_values == 0 ? invoke() : static_cast<cccl::detail::first_call_gate*>(build.first_call_state)->invoke(invoke);
#else
const int status =
fn(d_data.state, num_items, d_values.state, num_values, d_out.state, op.state, reinterpret_cast<void*>(stream));
#endif
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_binary_search(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_binary_search_build(
cccl_device_binary_search_build_result_t* build,
cccl_binary_search_mode_t mode,
cccl_iterator_t d_data,
cccl_iterator_t d_values,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_binary_search_build_ex(
build,
mode,
d_data,
d_values,
d_out,
op,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
CUresult cccl_device_binary_search_cleanup(cccl_device_binary_search_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
#if CCCL_OS(WINDOWS)
delete static_cast<cccl::detail::first_call_gate*>(build_ptr->first_call_state);
build_ptr->first_call_state = nullptr;
#endif
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->binary_search_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_binary_search_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,117 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstring>
#include <cccl/c/for.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// d_in_0, num_items, op_0_state, stream
using for_fn_t = int (*)(void*, unsigned long long, void*, void*);
CUresult cccl_device_for_build_ex(
cccl_device_for_build_result_t* build_ptr,
cccl_iterator_t d_data,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
auto result =
CubCall::from("cub/device/device_for.cuh")
.run("cub::DeviceFor::ForEachN")
.name("cccl_jit_for")
.with(in(d_data), num_items, for_each_op(op), stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->for_fn = result.fn_ptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_for_build_ex(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_for(
cccl_device_for_build_result_t build, cccl_iterator_t d_data, uint64_t num_items, cccl_op_t op, CUstream stream)
try
{
if (!build.for_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
auto fn = reinterpret_cast<for_fn_t>(build.for_fn);
const int status = fn(d_data.state, num_items, op.state, reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_for(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_for_build(
cccl_device_for_build_result_t* build,
cccl_iterator_t d_data,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_for_build_ex(
build, d_data, op, cc_major, cc_minor, cub_path, thrust_path, libcudacxx_path, ctk_path, nullptr);
}
CUresult cccl_device_for_cleanup(cccl_device_for_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->for_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_for_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,209 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cccl/c/histogram.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// JIT wrapper produced by CubCall:
// fn(temp, temp_bytes,
// d_samples, // input iterator state
// d_histogram, // output pointer (counter_t*)
// &num_levels, // int (host pointer)
// &lower_level, &upper_level, // level_t (host pointer)
// &num_row_pixels, // long long (host pointer)
// &num_rows, // long long (host pointer)
// &row_stride_bytes, // size_t (host-precomputed: row_stride_samples * sizeof(sample_t))
// stream)
using histogram_fn_t = int (*)(void*, size_t*, void*, void*, void*, void*, void*, void*, void*, void*, void*);
static constexpr cccl_type_info k_int_type{sizeof(int), alignof(int), CCCL_INT32};
static constexpr cccl_type_info k_int64_type{sizeof(long long), alignof(long long), CCCL_INT64};
static constexpr cccl_type_info k_size_type{sizeof(unsigned long long), alignof(unsigned long long), CCCL_UINT64};
CUresult cccl_device_histogram_build_ex(
cccl_device_histogram_build_result_t* build_ptr,
int num_channels,
int num_active_channels,
cccl_iterator_t d_samples,
int /*num_output_levels_val*/,
cccl_iterator_t d_output_histograms,
cccl_type_info level_type,
int64_t /*num_rows*/,
int64_t /*row_stride_samples*/,
bool /*is_evenly_segmented*/,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (num_channels != 1 || num_active_channels != 1)
{
fprintf(stderr,
"\nERROR in cccl_device_histogram_build(): only num_channels=1, num_active_channels=1 is "
"supported in the HostJIT path.\n");
return CUDA_ERROR_UNKNOWN;
}
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
// level_t comes from the build-time type info. CUB infers
// sample_t / counter_t from the iterator and output pointer respectively.
CubCallResult result =
CubCall::from("cub/device/device_histogram.cuh")
.run("cub::DeviceHistogram::HistogramEven")
.name("cccl_jit_histogram_even")
.with(temp_storage,
temp_bytes,
in(d_samples),
out(d_output_histograms),
typed_scalar(k_int_type, "num_levels"),
typed_scalar(level_type, "lower_level"),
typed_scalar(level_type, "upper_level"),
typed_scalar(k_int64_type, "num_row_pixels"),
typed_scalar(k_int64_type, "num_rows"),
typed_scalar(k_size_type, "row_stride_bytes"),
stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->histogram_fn = result.fn_ptr;
build_ptr->counter_type = d_output_histograms.value_type;
build_ptr->level_type = level_type;
build_ptr->sample_type = d_samples.value_type;
build_ptr->num_channels = num_channels;
build_ptr->num_active_channels = num_active_channels;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_histogram_build(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_histogram_build(
cccl_device_histogram_build_result_t* build,
int num_channels,
int num_active_channels,
cccl_iterator_t d_samples,
int num_output_levels_val,
cccl_iterator_t d_output_histograms,
cccl_type_info level_type,
int64_t num_rows,
int64_t row_stride_samples,
bool is_evenly_segmented,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_histogram_build_ex(
build,
num_channels,
num_active_channels,
d_samples,
num_output_levels_val,
d_output_histograms,
level_type,
num_rows,
row_stride_samples,
is_evenly_segmented,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
CUresult cccl_device_histogram_even(
cccl_device_histogram_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_samples,
cccl_iterator_t d_output_histograms,
cccl_value_t num_output_levels,
cccl_value_t lower_level,
cccl_value_t upper_level,
int64_t num_row_pixels,
int64_t num_rows,
int64_t row_stride_samples,
CUstream stream)
try
{
if (!build.histogram_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
// CUB takes row_stride_bytes (not samples). Pre-compute on the host so the
// JIT wrapper doesn't need a sizeof(sample_t) computation.
long long num_row_pixels_ll = static_cast<long long>(num_row_pixels);
long long num_rows_ll = static_cast<long long>(num_rows);
size_t row_stride_bytes = static_cast<size_t>(row_stride_samples) * build.sample_type.size;
auto fn = reinterpret_cast<histogram_fn_t>(build.histogram_fn);
const int status = fn(
d_temp_storage,
temp_storage_bytes,
d_samples.state,
d_output_histograms.state,
num_output_levels.state,
lower_level.state,
upper_level.state,
&num_row_pixels_ll,
&num_rows_ll,
&row_stride_bytes,
reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_histogram_even(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_histogram_cleanup(cccl_device_histogram_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->histogram_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_histogram_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,283 @@
cmake_minimum_required(VERSION 3.30)
# --------------------------------------------------------------------------
# LLVM/Clang/LLD — fetched via CPM as static libraries
# --------------------------------------------------------------------------
# CPM.cmake is at the cccl repo root: cccl/cmake/CPM.cmake
# From c/parallel.v2/src/hostjit/ that's ../../../../cmake/CPM.cmake
set(_cccl_cmake_dir "${CMAKE_CURRENT_SOURCE_DIR}/../../../../cmake")
if (EXISTS "${_cccl_cmake_dir}/CPM.cmake")
include("${_cccl_cmake_dir}/CPM.cmake")
else()
message(FATAL_ERROR "CPM.cmake not found at ${_cccl_cmake_dir}/CPM.cmake")
endif()
if (MSVC AND CMAKE_BUILD_TYPE STREQUAL "Debug")
message(
FATAL_ERROR
"hostjit does not support Debug builds on Windows. "
"The statically-linked LLVM Debug build is too large and causes stack "
"overflows at runtime. Use MinSizeRel, Release, or RelWithDebInfo instead."
)
endif()
set(HOSTJIT_LLVM_VERSION "llvmorg-22.1.1" CACHE STRING "LLVM git tag to fetch")
# List options must be set before CPMAddPackage
set(LLVM_ENABLE_PROJECTS "clang;lld" CACHE STRING "" FORCE)
set(LLVM_TARGETS_TO_BUILD "X86;NVPTX" CACHE STRING "" FORCE)
CPMAddPackage(
NAME llvm_project
GIT_REPOSITORY https://github.com/llvm/llvm-project.git
GIT_TAG ${HOSTJIT_LLVM_VERSION}
GIT_SHALLOW ON
SOURCE_SUBDIR llvm
EXCLUDE_FROM_ALL YES
OPTIONS
"LLVM_BUILD_LLVM_C_DYLIB OFF"
"LLVM_BUILD_TOOLS OFF"
"LLVM_BUILD_UTILS OFF"
"LLVM_BUILD_RUNTIME OFF"
"LLVM_BUILD_RUNTIMES OFF"
"LLVM_INCLUDE_BENCHMARKS OFF"
"LLVM_INCLUDE_DOCS OFF"
"LLVM_INCLUDE_EXAMPLES OFF"
"LLVM_INCLUDE_RUNTIMES OFF"
"LLVM_INCLUDE_TESTS OFF"
"LLVM_INCLUDE_TOOLS ON"
"LLVM_INCLUDE_UTILS OFF"
"LLVM_ENABLE_ZLIB OFF"
"LLVM_ENABLE_ZSTD OFF"
"LLVM_ENABLE_TERMINFO OFF"
"LLVM_ENABLE_BINDINGS OFF"
"CLANG_BUILD_TOOLS OFF"
"CLANG_ENABLE_ARCMT OFF"
"CLANG_ENABLE_STATIC_ANALYZER OFF"
)
# Ensure the clang resource directory exists
file(
MAKE_DIRECTORY "${llvm_project_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}"
)
# Find CUDA toolkit (may already be found by parent)
if (NOT CUDAToolkit_FOUND)
find_package(CUDAToolkit)
endif()
# --------------------------------------------------------------------------
# hostjit library
# --------------------------------------------------------------------------
add_library(
cccl.c.parallel.v2.hostjit_lib
compiler.cpp
config.cpp
loader.cpp
jit_compiler.cpp
codegen/types.cpp
codegen/iterators.cpp
codegen/operators.cpp
codegen/bitcode.cpp
codegen/cub_call.cpp
)
# CCCL_SOURCE_DIR points to the cccl repo root
# From c/parallel.v2/src/hostjit -> c/parallel.v2/src -> c/parallel.v2 -> c -> cccl
cmake_path(GET CMAKE_CURRENT_SOURCE_DIR PARENT_PATH _src_dir) # c/parallel.v2/src
cmake_path(GET _src_dir PARENT_PATH _c_parallel_dir) # c/parallel.v2
cmake_path(GET _c_parallel_dir PARENT_PATH _c_dir) # c
cmake_path(GET _c_dir PARENT_PATH _cccl_root) # cccl
target_include_directories(
cccl.c.parallel.v2.hostjit_lib
PUBLIC
${CMAKE_CURRENT_SOURCE_DIR}/include
${_c_parallel_dir}/include
${llvm_project_SOURCE_DIR}/llvm/include
${llvm_project_BINARY_DIR}/include
${llvm_project_SOURCE_DIR}/clang/include
${llvm_project_BINARY_DIR}/tools/clang/include
${llvm_project_SOURCE_DIR}/lld/include
${llvm_project_BINARY_DIR}/tools/lld/include
)
target_compile_definitions(
cccl.c.parallel.v2.hostjit_lib
PRIVATE
CCCL_C_EXPERIMENTAL=1
CCCL_SOURCE_DIR="${_cccl_root}"
CLANG_RESOURCE_DIR="${llvm_project_BINARY_DIR}/lib/clang/${LLVM_VERSION_MAJOR}"
CLANG_HEADERS_DIR="${llvm_project_SOURCE_DIR}/clang/lib/Headers"
HOSTJIT_INCLUDE_DIR="${CMAKE_CURRENT_SOURCE_DIR}/include"
)
if (CUDAToolkit_FOUND)
target_include_directories(
cccl.c.parallel.v2.hostjit_lib
PUBLIC ${CUDAToolkit_INCLUDE_DIRS}
)
cmake_path(GET CUDAToolkit_BIN_DIR PARENT_PATH CUDA_TOOLKIT_ROOT_FROM_CMAKE)
target_compile_definitions(
cccl.c.parallel.v2.hostjit_lib
PRIVATE
CUDA_TOOLKIT_PATH="${CUDA_TOOLKIT_ROOT_FROM_CMAKE}"
CUDA_SDK_VERSION="${CUDAToolkit_VERSION_MAJOR}.0"
)
endif()
# Link against LLVM/Clang/LLD
target_link_libraries(
cccl.c.parallel.v2.hostjit_lib
PUBLIC
# LLVM
LLVMCore
LLVMSupport
LLVMIRReader
LLVMMC
LLVMObject
LLVMX86CodeGen
LLVMX86AsmParser
LLVMX86Desc
LLVMX86Info
LLVMNVPTXCodeGen
LLVMNVPTXDesc
LLVMNVPTXInfo
LLVMLinker
LLVMPasses
# Clang
clangAST
clangBasic
clangCodeGen
clangDriver
clangFrontend
clangFrontendTool
clangLex
clangParse
clangSema
clangEdit
clangAnalysis
clangRewrite
clangSerialization
# LLD
$<IF:$<PLATFORM_ID:Windows>,lldCOFF,lldELF>
lldCommon
)
if (NOT WIN32)
target_link_libraries(cccl.c.parallel.v2.hostjit_lib PUBLIC dl)
endif()
if (CUDAToolkit_FOUND)
target_link_libraries(
cccl.c.parallel.v2.hostjit_lib
PUBLIC CUDA::cuda_driver CUDA::cudart
)
if (WIN32)
# On Windows, static CUDA libs are built with /MT which conflicts with
# the project's dynamic CRT (/MD). Use dynamic variants instead.
target_link_libraries(
cccl.c.parallel.v2.hostjit_lib
PUBLIC CUDA::nvJitLink CUDA::nvfatbin
)
else()
# Prefer static CUDA libs on Linux for self-contained binaries. If the
# toolchain (e.g. lite/pip CUDA installs or some Docker images) only ships
# the dynamic variants, fall back to those rather than failing configure.
foreach (_cudalib nvJitLink nvptxcompiler nvfatbin)
if (TARGET "CUDA::${_cudalib}_static")
target_link_libraries(
cccl.c.parallel.v2.hostjit_lib
PUBLIC "CUDA::${_cudalib}_static"
)
elseif (TARGET "CUDA::${_cudalib}")
target_link_libraries(
cccl.c.parallel.v2.hostjit_lib
PUBLIC "CUDA::${_cudalib}"
)
else()
message(
FATAL_ERROR
"hostjit needs CUDA::${_cudalib}[_static] but neither variant was "
"found by FindCUDAToolkit. Install the full CUDA toolkit "
"(libnvjitlink-dev / libnvfatbin-dev or equivalent)."
)
endif()
endforeach()
endif()
endif()
if (NOT MSVC)
target_compile_options(cccl.c.parallel.v2.hostjit_lib PRIVATE -fno-rtti)
endif()
set_target_properties(
cccl.c.parallel.v2.hostjit_lib
PROPERTIES CXX_STANDARD 20 POSITION_INDEPENDENT_CODE ON
)
# --------------------------------------------------------------------------
# Install clang headers into wheel (for self-sufficient packaging)
# --------------------------------------------------------------------------
# Clang CUDA headers we still use from the LLVM source tree.
# We DON'T install device_functions, math, or libdevice_declares — our local
# copies in cuda_minimal/ replace them.
set(
_clang_cuda_headers_needed
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_math_forward_declares.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_builtin_vars.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_cmath.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_intrinsics.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_complex_builtins.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__clang_cuda_texture_intrinsics.h"
)
install(
FILES ${_clang_cuda_headers_needed}
DESTINATION "cuda/cccl/headers/clang"
)
# Clang builtin C headers needed by our stubs and CUDA toolkit headers.
file(
GLOB _clang_stddef_headers
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__stddef_*.h"
)
set(
_clang_c_headers
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/limits.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/stddef.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/stdint.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__stddef_header_macro.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/float.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/__float_header_macro.h"
"${llvm_project_SOURCE_DIR}/clang/lib/Headers/inttypes.h"
${_clang_stddef_headers}
)
install(FILES ${_clang_c_headers} DESTINATION "cuda/cccl/headers/clang")
# Hostjit's minimal CUDA runtime headers (replacements for upstream clang headers)
set(
_hostjit_cuda_minimal_dir
"${CMAKE_CURRENT_SOURCE_DIR}/include/hostjit/cuda_minimal"
)
file(GLOB _hostjit_cuda_minimal_headers "${_hostjit_cuda_minimal_dir}/*.h")
install(
FILES ${_hostjit_cuda_minimal_headers}
DESTINATION "cuda/cccl/headers/hostjit/cuda_minimal"
)
# Hostjit's stub headers (minimal C++ standard library stubs for device compilation)
# Use GLOB_RECURSE + DIRECTORY so subdirectory overrides (e.g. cuda/std/__cstdlib/)
# are also installed alongside the top-level stubs.
install(
DIRECTORY "${_hostjit_cuda_minimal_dir}/stubs/"
DESTINATION "cuda/cccl/headers/hostjit/cuda_minimal/stubs"
)
# On Windows with multi-config generators (Visual Studio), exclude hostjit
# targets from Debug builds — the LLVM Debug build causes stack overflows.
if (MSVC)
set_target_properties(
cccl.c.parallel.v2.hostjit_lib
PROPERTIES EXCLUDE_FROM_DEFAULT_BUILD_DEBUG TRUE
)
endif()

View File

@@ -0,0 +1,237 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <filesystem>
#include <fstream>
#include <functional>
#include <stdexcept>
#include <string_view>
#include <hostjit/codegen/bitcode.hpp>
#include <hostjit/compiler.hpp>
namespace hostjit::codegen
{
namespace
{
bool write_file(const char* data, size_t size, const std::string& path)
{
std::ofstream f(path, std::ios::binary);
if (!f)
{
return false;
}
f.write(data, static_cast<std::streamsize>(size));
return f.good();
}
std::string make_temp_path(const std::string& prefix, uintptr_t id, const std::string& ext)
{
return (std::filesystem::temp_directory_path() / (prefix + std::to_string(id) + ext)).string();
}
} // anonymous namespace
BitcodeCollector::BitcodeCollector(CompilerConfig& config, uintptr_t unique_id)
: config_(config)
, unique_id_(unique_id)
{}
bool BitcodeCollector::is_bitcode_op(cccl_op_t op)
{
return (op.code_type == CCCL_OP_LLVM_IR || op.code_type == CCCL_OP_LTOIR) && op.code != nullptr && op.code_size > 0;
}
void BitcodeCollector::add_raw_bitcode(const char* data, size_t size, const std::string& name)
{
if (!data || size == 0)
{
return;
}
// Dedup by content hash: identical bitcode bytes define identical symbols
// (e.g. two PointerIterator<int>s sharing the same advance LTOIR). Adding
// both would make nvJitLink fail with "symbol multiply defined".
const auto hash = std::hash<std::string_view>{}(std::string_view(data, size));
if (!added_content_hashes_.insert(hash).second)
{
return; // exact same bytes already added
}
// Magic-byte routing: LLVM bitcode starts with "BC" (0x42 0x43) and goes to
// LLVM's bitcode linker so it can be inlined into the CUB module at the IR
// level. Anything else is treated as LTO-IR (binary fatbin container) and
// fed to nvJitLink. CPP_SOURCE never reaches here: main ops are dispatched
// by code_type in add_op_code, and per-extra C++ source is dispatched by
// extra_code_types[i] in the extras loop below — both call compile_and_add
// directly.
const bool is_llvm_bitcode =
size >= 2 && static_cast<unsigned char>(data[0]) == 0x42 && static_cast<unsigned char>(data[1]) == 0x43;
const char* ext = is_llvm_bitcode ? ".bc" : ".ltoir";
auto path = make_temp_path("cccl_" + name + "_", unique_id_, ext);
if (!write_file(data, size, path))
{
return;
}
if (is_llvm_bitcode)
{
config_.device_bitcode_files.push_back(path);
}
else
{
config_.device_ltoir_files.push_back(path);
}
temp_paths_.push_back(path);
}
bool BitcodeCollector::compile_and_add(const char* source, size_t source_size, const std::string& name)
{
// Dedup by source-content hash: two PointerIterator<int> children in the
// same zip produce identical CPP source that defines the same symbol; without
// this guard the LLVM linker fails with "symbol multiply defined".
const auto hash = std::hash<std::string_view>{}(std::string_view(source, source_size));
if (!added_content_hashes_.insert(hash).second)
{
return true;
}
hostjit::CUDACompiler compiler;
std::string src(source, source_size);
auto result = compiler.compileToDeviceBitcode(src, config_);
if (!result.success)
{
fprintf(stderr, "\nERROR compiling %s to bitcode: %s\n", name.c_str(), result.diagnostics.c_str());
return false;
}
auto path = make_temp_path("cccl_" + name + "_", unique_id_, ".bc");
if (write_file(result.bitcode.data(), result.bitcode.size(), path))
{
config_.device_bitcode_files.push_back(path);
temp_paths_.push_back(path);
return true;
}
return false;
}
void BitcodeCollector::add_op_code(cccl_op_t& op, const std::string& name)
{
if (!op.code || op.code_size == 0)
{
return;
}
// Deduplicate: if two iterators share the same symbol (e.g. two CountingIterators
// of the same type), only compile/link the bitcode once.
if (op.name && op.name[0])
{
if (!added_symbols_.insert(std::string(op.name)).second)
{
return; // already added
}
}
if (op.code_type == CCCL_OP_CPP_SOURCE)
{
compile_and_add(op.code, op.code_size, name);
}
else
{
add_raw_bitcode(op.code, op.code_size, name);
}
// Also link any extra modules (child iterator ops, numba-compiled ops).
int extra_counter = 0;
if (op.num_extra_ltoirs > 0 && (!op.extra_ltoirs || !op.extra_ltoir_sizes))
{
throw std::runtime_error("cccl_op_t: extra_ltoirs and extra_ltoir_sizes must be non-null when num_extra_ltoirs > "
"0");
}
for (size_t i = 0; i < op.num_extra_ltoirs; ++i)
{
if (op.extra_ltoirs[i] && op.extra_ltoir_sizes[i] > 0)
{
auto extra_name = name + "_extra" + std::to_string(extra_counter++);
const auto* data = op.extra_ltoirs[i];
const auto data_sz = op.extra_ltoir_sizes[i];
if (!op.extra_code_types)
{
throw std::runtime_error("cccl_op_t: extra_code_types must be non-null when num_extra_ltoirs > 0");
}
const cccl_op_code_type t = op.extra_code_types[i];
if (t == CCCL_OP_CPP_SOURCE)
{
compile_and_add(data, data_sz, extra_name);
}
else
{
add_raw_bitcode(data, data_sz, extra_name);
}
}
}
}
void BitcodeCollector::add_op(cccl_op_t op, const std::string& label)
{
// Only add bitcode for LTOIR/LLVM_IR ops (CPP_SOURCE is embedded inline in the generated source)
if (is_bitcode_op(op))
{
add_raw_bitcode(op.code, op.code_size, label);
}
// Always process extras with per-entry dispatch.
int extra_counter = 0;
if (op.num_extra_ltoirs > 0 && (!op.extra_ltoirs || !op.extra_ltoir_sizes))
{
throw std::runtime_error("cccl_op_t: extra_ltoirs and extra_ltoir_sizes must be non-null when num_extra_ltoirs > "
"0");
}
for (size_t i = 0; i < op.num_extra_ltoirs; ++i)
{
if (op.extra_ltoirs[i] && op.extra_ltoir_sizes[i] > 0)
{
auto extra_name = label + "_extra" + std::to_string(extra_counter++);
const auto* data = op.extra_ltoirs[i];
const auto data_sz = op.extra_ltoir_sizes[i];
if (!op.extra_code_types)
{
throw std::runtime_error("cccl_op_t: extra_code_types must be non-null when num_extra_ltoirs > 0");
}
const cccl_op_code_type t = op.extra_code_types[i];
if (t == CCCL_OP_CPP_SOURCE)
{
compile_and_add(data, data_sz, extra_name);
}
else
{
add_raw_bitcode(data, data_sz, extra_name);
}
}
}
}
void BitcodeCollector::add_iterator(cccl_iterator_t it, const std::string& label_prefix)
{
if (it.type != CCCL_ITERATOR)
{
return;
}
add_op_code(it.advance, label_prefix + "_adv");
add_op_code(it.dereference, label_prefix + "_deref");
}
void BitcodeCollector::cleanup()
{
for (const auto& p : temp_paths_)
{
std::filesystem::remove(p);
}
temp_paths_.clear();
}
} // namespace hostjit::codegen

View File

@@ -0,0 +1,812 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstdlib>
#include <cstring>
#include <filesystem>
#include <format>
#include <fstream>
#include <map>
#include <memory>
#include <stdexcept>
#include <hostjit/codegen/bitcode.hpp>
#include <hostjit/codegen/cub_call.hpp>
#include <hostjit/codegen/iterators.hpp>
#include <hostjit/codegen/operators.hpp>
#include <hostjit/codegen/types.hpp>
namespace hostjit::codegen
{
CubCall CubCall::from(const char* include_header)
{
CubCall c;
c.include_ = include_header;
return c;
}
CubCall& CubCall::run(const char* cub_function)
{
cub_function_ = cub_function;
return *this;
}
CubCall& CubCall::name(const char* export_name)
{
fn_name_ = export_name;
return *this;
}
// Helper to find the accumulator type from the argument list.
// Priority: first cccl_value_t, then first input_t's value_type.
namespace
{
cccl_type_info find_accum_type(const std::vector<Arg>& args)
{
// Highest priority: explicit override
for (const auto& arg : args)
{
if (auto* fa = std::get_if<force_accum_type_t>(&arg))
{
return fa->type;
}
}
// First: look for cccl_value_t (init value defines accum type)
for (const auto& arg : args)
{
if (auto* val = std::get_if<cccl_value_t>(&arg))
{
return val->type;
}
}
// Second: future_val_t carries explicit type info
for (const auto& arg : args)
{
if (auto* fv = std::get_if<future_val_t>(&arg))
{
return fv->type;
}
}
// Fallback: first input iterator's value_type
for (const auto& arg : args)
{
if (auto* inp = std::get_if<input_t>(&arg))
{
return inp->it.value_type;
}
}
// Last resort: first output iterator
for (const auto& arg : args)
{
if (auto* outp = std::get_if<output_t>(&arg))
{
return outp->it.value_type;
}
}
return cccl_type_info{sizeof(int), alignof(int), CCCL_INT32};
}
} // anonymous namespace
namespace
{
// Returns true if `args` contains an env_stream_t — used to decide whether the
// shared-includes block needs to pull in <cuda/std/__execution/env.h>.
bool needs_env_include(const std::vector<Arg>& args)
{
for (const auto& arg : args)
{
if (std::holds_alternative<env_stream_t>(arg))
{
return true;
}
}
return false;
}
// Emits the system #includes + the CUB header. Hoisted from source() so
// multi-function compiles can emit this once and wrap N function bodies in N
// namespaces below.
std::string shared_includes(const std::string& cub_include, bool needs_tuple, bool needs_env)
{
std::string src = R"(#include <cuda_runtime.h>
#include <cuda_fp16.h>
#include <cuda/std/iterator>
#include <cuda/std/functional>
#include <cuda/functional>
)";
if (needs_tuple)
{
src += "#include <cuda/std/tuple>\n";
}
if (needs_env)
{
// Use the narrow internal env.h header rather than <cuda/std/execution>
// — the umbrella header pulls in pstl machinery that depends on <vector>
// and exception types not available in the hostjit environment.
src += "#include <cuda/std/__execution/env.h>\n";
src += "#include <cuda/stream_ref>\n";
}
src += std::format("#include <{}>\n\n", cub_include);
return src;
}
} // namespace
std::string CubCall::source() const
{
// Single-function source = shared includes + this CubCall's body.
return shared_includes(include_, tuple_inputs_, needs_env_include(args_)) + body();
}
std::string CubCall::body() const
{
// Pass 1: determine accumulator type
cccl_type_info accum_info = find_accum_type(args_);
std::string accum_preamble;
std::string accum_type = resolve_type(accum_info, "storage_t", accum_preamble);
// Counters for unique naming
int in_count = 0;
int out_count = 0;
int op_count = 0;
int val_count = 0;
// Accumulated sections
std::string preamble;
std::vector<std::string> params;
std::vector<std::string> setup_lines;
std::vector<std::string> cub_args;
// Lines emitted after the cub::DeviceX::Y(...) call and before the return —
// populated by post-call tags (e.g. selector_out_t capturing a DoubleBuffer's
// selector member).
std::vector<std::string> post_call_lines;
// Emit accum type
if (!accum_preamble.empty())
{
preamble += accum_preamble;
}
preamble += std::format("using accum_t = {};\n\n", accum_type);
// Shared alias cache: (size, alignment) → type name.
// Multiple iterators with the same unknown struct layout must share a single C++
// type so that CUB can move data between them (e.g. merge sort block loads).
std::map<std::pair<size_t, size_t>, std::string> struct_type_map;
int struct_type_counter = 0;
// Return a stable C++ element-type name for an iterator's value_type:
// - Known C type → C++ keyword (e.g. "int", "float")
// - Struct matching accum_t → "accum_t" (preserves operator compatibility)
// - Other struct → shared alias for this (size, alignment) layout
// Built-in C type sizes (CCCL_TYPE_ENUM → bytes). Used to detect a
// mismatch where the caller reports a primitive `vt.type` but `vt.size`
// says the element is wider — common when a custom struct happens to
// share the primitive's tag. In that case fall through to a storage
// struct so the iterator strides correctly.
auto builtin_size = [](cccl_type_enum t) -> size_t {
switch (t)
{
case CCCL_INT8:
case CCCL_UINT8:
case CCCL_BOOLEAN:
return 1;
case CCCL_INT16:
case CCCL_UINT16:
case CCCL_FLOAT16:
return 2;
case CCCL_INT32:
case CCCL_UINT32:
case CCCL_FLOAT32:
return 4;
case CCCL_INT64:
case CCCL_UINT64:
case CCCL_FLOAT64:
return 8;
default:
return 0;
}
};
auto iter_elem_type_name = [&](const cccl_type_info& vt) -> std::string {
auto name = get_type_name(vt.type);
if (!name.empty() && vt.size == builtin_size(vt.type))
{
return name;
}
if (vt.size == accum_info.size && vt.alignment == accum_info.alignment && vt.type == accum_info.type)
{
return "accum_t";
}
auto key = std::make_pair(vt.size, vt.alignment);
auto it = struct_type_map.find(key);
if (it != struct_type_map.end())
{
return it->second;
}
auto alias = std::format("__cccl_struct_{}_t", struct_type_counter++);
preamble += make_storage_type(alias.c_str(), vt.size, vt.alignment);
struct_type_map[key] = alias;
return alias;
};
// Pass 2: process each argument
for (const auto& arg : args_)
{
std::visit(
[&](auto&& a) {
using T = std::decay_t<decltype(a)>;
if constexpr (std::is_same_v<T, temp_storage_t>)
{
params.push_back("void* d_temp_storage");
cub_args.push_back("d_temp_storage");
}
else if constexpr (std::is_same_v<T, temp_bytes_t>)
{
params.push_back("size_t* temp_storage_bytes");
cub_args.push_back("*temp_storage_bytes");
}
else if constexpr (std::is_same_v<T, num_items_t>)
{
params.push_back(std::format("unsigned long long {}", a.name));
cub_args.push_back(std::format("(unsigned long long){}", a.name));
}
else if constexpr (std::is_same_v<T, stream_t>)
{
params.push_back("void* stream");
cub_args.push_back("(cudaStream_t)stream");
}
else if constexpr (std::is_same_v<T, env_stream_t>)
{
params.push_back("void* stream");
cub_args.push_back("::cuda::std::execution::env{::cuda::stream_ref{(cudaStream_t)stream}}");
}
else if constexpr (std::is_same_v<T, input_t>)
{
auto idx = in_count++;
auto struct_name = std::format("in_{}_it_t", idx);
auto var_name = std::format("in_{}", idx);
auto param_name = std::format("d_in_{}", idx);
auto value_type = iter_elem_type_name(a.it.value_type);
auto code = make_input_iterator(a.it, value_type, "accum_t", struct_name, var_name, param_name);
preamble += code.preamble;
params.push_back(std::format("void* {}", param_name));
setup_lines.push_back(code.setup_code);
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, output_t>)
{
auto idx = out_count++;
auto struct_name = std::format("out_{}_it_t", idx);
auto var_name = std::format("out_{}", idx);
auto param_name = std::format("d_out_{}", idx);
auto value_type = iter_elem_type_name(a.it.value_type);
auto code = make_output_iterator(a.it, "accum_t", struct_name, var_name, param_name, value_type);
preamble += code.preamble;
params.push_back(std::format("void* {}", param_name));
setup_lines.push_back(code.setup_code);
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, cccl_op_t>)
{
auto idx = op_count++;
auto functor_name = std::format("Op_{}", idx);
auto var_name = std::format("op_{}", idx);
auto state_param = std::format("op_{}_state", idx);
bool has_bc = BitcodeCollector::is_bitcode_op(a);
auto code = make_binary_op(a, accum_type, functor_name, var_name, state_param, has_bc);
preamble += code.preamble;
// Always emit op_state param for ABI stability (unused for stateless ops)
params.push_back(std::format("void* {}", state_param));
setup_lines.push_back(code.setup_code);
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, cmp_t>)
{
auto idx = op_count++;
auto functor_name = std::format("CmpOp_{}", idx);
auto var_name = std::format("cmp_{}", idx);
auto state_param = std::format("cmp_{}_state", idx);
bool has_bc = BitcodeCollector::is_bitcode_op(a.op);
auto code = make_comparison_op(a.op, accum_type, functor_name, var_name, state_param, has_bc);
preamble += code.preamble;
params.push_back(std::format("void* {}", state_param));
setup_lines.push_back(code.setup_code);
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, for_each_op_t>)
{
auto idx = op_count++;
auto functor_name = std::format("ForEachOp_{}", idx);
auto var_name = std::format("op_{}", idx);
auto state_param = std::format("op_{}_state", idx);
bool has_bc = BitcodeCollector::is_bitcode_op(a.op);
// The element type is the first input iterator's value_type, which
// CubCall has already resolved via find_accum_type.
const std::string elem_type = iter_elem_type_name(accum_info);
auto code = make_for_each_op(a.op, elem_type, functor_name, var_name, state_param, has_bc);
preamble += code.preamble;
params.push_back(std::format("void* {}", state_param));
setup_lines.push_back(code.setup_code);
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, double_buffer_t>)
{
// Emit two void* params (in/out buffer state pointers), construct a
// cub::DoubleBuffer<elem_t> local with the given var_name, and pass
// the buffer to the CUB call. iter_elem_type_name resolves the
// element type the same way input/output iterators do.
const std::string elem_type = iter_elem_type_name(a.in_it.value_type);
const std::string var_name = a.var_name;
const auto in_param = var_name + "_in_state";
const auto out_param = var_name + "_out_state";
params.push_back(std::format("void* {}", in_param));
params.push_back(std::format("void* {}", out_param));
setup_lines.push_back(std::format(
"cub::DoubleBuffer<{0}> {1}(static_cast<{0}*>({2}), static_cast<{0}*>({3}));",
elem_type,
var_name,
in_param,
out_param));
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, selector_out_t>)
{
// Emit a void* selector_out param and capture <buffer>.selector after
// the CUB call. Paired with a double_buffer_t whose var_name matches.
params.push_back("void* selector_out");
post_call_lines.push_back(std::format("*static_cast<int*>(selector_out) = {}.selector;", a.buffer_var_name));
}
else if constexpr (std::is_same_v<T, unary_op_t>)
{
auto idx = op_count++;
auto functor_name = std::format("UnaryOp_{}", idx);
auto var_name = std::format("op_{}", idx);
auto state_param = std::format("op_{}_state", idx);
bool has_bc = BitcodeCollector::is_bitcode_op(a.op);
// For unknown types the iterators use accum_t as fallback; the unary
// op functor must use the same names so CUB can match the types.
// Reuse the iterator's element-type resolver so a primitive `vt.type`
// with a custom-sized `vt.size` falls back to the same storage alias
// the iterator uses, rather than naming the wider element "int".
std::string in_type = iter_elem_type_name(a.in_type);
std::string out_type = iter_elem_type_name(a.out_type);
auto code = make_unary_op(a.op, in_type, out_type, functor_name, var_name, state_param, has_bc);
preamble += code.preamble;
params.push_back(std::format("void* {}", state_param));
setup_lines.push_back(code.setup_code);
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, force_accum_type_t>)
{
// No-op: only influences accum type resolution, generates no code.
}
else if constexpr (std::is_same_v<T, future_val_t>)
{
auto idx = val_count++;
auto var_name = std::format("future_{}", idx);
auto param_name = std::format("future_{}_param", idx);
// The caller passes a device pointer; we wrap it in FutureValue<accum_t>
// so CUB fetches the init value from device memory at scan time.
params.push_back(std::format("void* {}", param_name));
setup_lines.push_back(
std::format("cub::FutureValue<accum_t> {}(static_cast<accum_t*>({}));", var_name, param_name));
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, cccl_value_t>)
{
auto idx = val_count++;
auto var_name = std::format("val_{}", idx);
auto param_name = std::format("val_{}_ptr", idx);
params.push_back(std::format("void* {}", param_name));
setup_lines.push_back(std::format(
"accum_t {};\n __builtin_memcpy(&{}, {}, sizeof(accum_t));", var_name, var_name, param_name));
cub_args.push_back(var_name);
}
else if constexpr (std::is_same_v<T, typed_scalar_t>)
{
// Caller passes a host pointer; we memcpy onto the stack as the
// requested C++ type before calling CUB. The C++ type comes from
// a.type (cccl_type_info), the wrapper parameter is named
// `<a.name>_ptr`, and the local that CUB sees is `<a.name>`.
const std::string cpp_type = resolve_type(a.type, a.name, preamble);
const auto param_name = std::string(a.name) + "_ptr";
params.push_back(std::format("void* {}", param_name));
setup_lines.push_back(
std::format("{0} {1};\n __builtin_memcpy(&{1}, {2}, sizeof({0}));", cpp_type, a.name, param_name));
cub_args.push_back(a.name);
}
},
arg);
}
// When tuple_inputs_ is set, replace the individual input cub_args with a
// single make_tuple(...) expression covering all of them.
if (tuple_inputs_ && in_count > 1)
{
// Collect the first in_count cub_args that correspond to input iterators.
// Inputs are emitted first among iterator args, so they occupy the leading
// cub_args entries (after temp_storage/temp_bytes if present).
// Reconstruct: find and replace the in_0..in_N-1 vars with make_tuple.
std::vector<std::string> input_vars;
std::vector<std::string> other_args;
for (const auto& a : cub_args)
{
// Input vars are named "in_0", "in_1", etc.
if (a.starts_with("in_") && a.size() >= 4 && std::isdigit(a[3]))
{
input_vars.push_back(a);
}
else
{
other_args.push_back(a);
}
}
std::string tuple_arg = "::cuda::std::make_tuple(";
for (size_t i = 0; i < input_vars.size(); ++i)
{
if (i)
{
tuple_arg += ", ";
}
tuple_arg += input_vars[i];
}
tuple_arg += ")";
// Rebuild cub_args: replace all in_* with the single tuple arg (at original position of in_0)
cub_args.clear();
cub_args.push_back(tuple_arg);
for (const auto& a : other_args)
{
cub_args.push_back(a);
}
}
// Assemble the per-function body: preamble + extern "C" function defn.
// System #includes live in shared_includes(), emitted once at TU scope by
// either source() (single-fn) or compile() (multi-fn).
std::string src = preamble;
// Function signature
src += std::format("extern \"C\" _CCCL_VISIBILITY_EXPORT int {}(\n", fn_name_);
for (size_t i = 0; i < params.size(); ++i)
{
src += " " + params[i];
if (i + 1 < params.size())
{
src += ",\n";
}
}
src += ")\n{\n";
// Setup code
for (const auto& line : setup_lines)
{
src += " " + line + "\n";
}
src += "\n";
// CUB call
src += std::format(" cudaError_t err = {}(\n", cub_function_);
for (size_t i = 0; i < cub_args.size(); ++i)
{
src += " " + cub_args[i];
if (i + 1 < cub_args.size())
{
src += ",\n";
}
}
src += ");\n\n";
// Post-call lines (e.g., capturing a DoubleBuffer's selector).
for (const auto& line : post_call_lines)
{
src += " " + line + "\n";
}
if (!post_call_lines.empty())
{
src += "\n";
}
// Error return
src += R"( return (int)err;
}
)";
return src;
}
hostjit::CompilerConfig CubCall::make_jit_config(
int cc_major,
int cc_minor,
cccl_build_config* config,
const char* ctk_path,
const char* cccl_include_path,
const std::string& entry_point_name)
{
auto jit_config = hostjit::detectDefaultConfig();
jit_config.sm_version = cc_major * 10 + cc_minor;
jit_config.verbose = false;
jit_config.entry_point_name = entry_point_name;
if (ctk_path && ctk_path[0] != '\0')
{
jit_config.cuda_toolkit_path = ctk_path;
// Rebuild library_paths from the new toolkit root so the linker
// can find libcudart.so in the pip-installed layout.
jit_config.library_paths.clear();
for (const char* subdir : {"lib64", "lib"})
{
auto candidate = std::filesystem::path(ctk_path) / subdir;
if (std::filesystem::exists(candidate))
{
jit_config.library_paths.push_back(candidate.string());
}
}
}
if (cccl_include_path && cccl_include_path[0] != '\0')
{
jit_config.cccl_include_path = cccl_include_path;
// When CCCL headers are pip-installed, the hostjit cuda_minimal headers
// are installed alongside them under the parent directory:
// cccl_include_path = .../cuda/cccl/headers/include/
// hostjit headers = .../cuda/cccl/headers/hostjit/cuda_minimal/
// So derive hostjit_include_path as the parent of cccl_include_path.
if (jit_config.hostjit_include_path.empty()
|| !std::filesystem::exists(jit_config.hostjit_include_path + "/hostjit/cuda_minimal"))
{
auto parent = std::filesystem::path(cccl_include_path).parent_path().string();
if (std::filesystem::exists(parent + "/hostjit/cuda_minimal"))
{
jit_config.hostjit_include_path = parent;
}
}
}
if (config)
{
for (size_t i = 0; i < config->num_extra_include_dirs; ++i)
{
jit_config.include_paths.push_back(config->extra_include_dirs[i]);
}
for (size_t i = 0; i < config->num_extra_compile_flags; ++i)
{
std::string_view flag = config->extra_compile_flags[i];
if (flag.starts_with("-D"))
{
flag.remove_prefix(2);
if (auto eq = flag.find('='); eq != std::string_view::npos)
{
jit_config.macro_definitions[std::string{flag.substr(0, eq)}] = std::string{flag.substr(eq + 1)};
}
else
{
jit_config.macro_definitions[std::string{flag}] = "";
}
}
}
jit_config.enable_pch = config->enable_pch != 0;
jit_config.verbose = config->verbose != 0;
}
return jit_config;
}
CubCallResult CubCall::compile(
int cc_major, int cc_minor, cccl_build_config* config, const char* ctk_path, const char* cccl_include_path) const
{
// 1. Configure compiler
auto jit_config = make_jit_config(cc_major, cc_minor, config, ctk_path, cccl_include_path, fn_name_);
// 2. Auto-collect bitcode from ops and iterators
uintptr_t unique_id = reinterpret_cast<uintptr_t>(this);
BitcodeCollector bitcode(jit_config, unique_id);
int op_idx = 0;
int in_idx = 0;
int out_idx = 0;
collect_bitcode(bitcode, op_idx, in_idx, out_idx);
// 3. Generate source
std::string cuda_source = source();
if (const char* dump_path = std::getenv("CUBCALL_DUMP_SOURCE"))
{
std::ofstream f(dump_path);
f << cuda_source;
}
// 4. Compile. unique_ptr ensures the JITCompiler is freed if the next two
// checks throw; .release() transfers ownership to CubCallResult on success.
auto compiler = std::make_unique<JITCompiler>(jit_config);
if (!compiler->compile(cuda_source))
{
std::string err = compiler->getLastError();
bitcode.cleanup();
throw std::runtime_error("CubCall compilation failed: " + err);
}
bitcode.cleanup();
// 5. Extract function pointer
using fn_t = int (*)(void*, ...);
auto fn = compiler->getFunction<fn_t>(fn_name_);
if (!fn)
{
throw std::runtime_error("CubCall function lookup failed: " + compiler->getLastError());
}
// 6. Copy cubin
auto cubin = compiler->getCubin();
return CubCallResult{compiler.release(), reinterpret_cast<void*>(fn), std::move(cubin)};
}
void CubCall::collect_bitcode(BitcodeCollector& bitcode, int& op_idx, int& in_idx, int& out_idx) const
{
for (const auto& arg : args_)
{
std::visit(
[&](auto&& a) {
using T = std::decay_t<decltype(a)>;
if constexpr (std::is_same_v<T, cccl_op_t>)
{
bitcode.add_op(a, std::format("op_{}", op_idx++));
}
else if constexpr (std::is_same_v<T, cmp_t>)
{
bitcode.add_op(a.op, std::format("cmp_{}", op_idx++));
}
else if constexpr (std::is_same_v<T, unary_op_t>)
{
bitcode.add_op(a.op, std::format("op_{}", op_idx++));
}
else if constexpr (std::is_same_v<T, for_each_op_t>)
{
bitcode.add_op(a.op, std::format("op_{}", op_idx++));
}
else if constexpr (std::is_same_v<T, input_t>)
{
bitcode.add_iterator(a.it, std::format("in_{}", in_idx++));
}
else if constexpr (std::is_same_v<T, output_t>)
{
bitcode.add_iterator(a.it, std::format("out_{}", out_idx++));
}
},
arg);
}
}
MultiCubCallResult CubCall::compile(
std::initializer_list<CubCall> calls,
int cc_major,
int cc_minor,
cccl_build_config* config,
const char* ctk_path,
const char* cccl_include_path)
{
if (calls.size() == 0)
{
throw std::runtime_error("CubCall::compile: empty CubCall list");
}
// All CubCalls must share the same CUB header — we emit it once at the top
// of the merged TU. (If a future use case needs heterogeneous includes,
// extend this to union the set; for now keep it strict so silent mismatches
// can't slip through.)
const std::string& shared_include = calls.begin()->include_;
for (const auto& cb : calls)
{
if (cb.include_ != shared_include)
{
throw std::runtime_error("CubCall::compile: all CubCalls in a multi-compile must share the same .from(include) "
"header");
}
}
// Detect whether any CubCall needs the env / tuple system includes.
bool any_tuple = false;
bool any_env = false;
for (const auto& cb : calls)
{
any_tuple = any_tuple || cb.tuple_inputs_;
any_env = any_env || needs_env_include(cb.args_);
}
// entry_point_name is used to mark a single function as preserved during
// internalization. Use the first CubCall's name as the primary entry; the
// others will still be exported via extern "C" _CCCL_VISIBILITY_EXPORT so dlsym finds them.
auto jit_config = make_jit_config(cc_major, cc_minor, config, ctk_path, cccl_include_path, calls.begin()->fn_name_);
// Shared BitcodeCollector across all CubCalls — identical user-op or
// iterator bitcode referenced from multiple wrappers gets deduplicated by
// content hash + symbol name inside the collector.
uintptr_t unique_id = reinterpret_cast<uintptr_t>(&*calls.begin());
BitcodeCollector bitcode(jit_config, unique_id);
int op_idx = 0;
int in_idx = 0;
int out_idx = 0;
for (const auto& cb : calls)
{
cb.collect_bitcode(bitcode, op_idx, in_idx, out_idx);
}
// Build the merged source: shared includes at TU scope, then one
// `namespace fn_<i> { ... body() }` per CubCall.
// The extern "C" _CCCL_VISIBILITY_EXPORT symbols defined inside each
// namespace export under the global C-linkage name (no mangling),
// so dlsym(handle, cb.fn_name_) finds them.
std::string cuda_source = shared_includes(shared_include, any_tuple, any_env);
int i = 0;
for (const auto& cb : calls)
{
cuda_source += std::format("namespace fn_{} {{\n", i);
cuda_source += cb.body();
cuda_source += std::format("}} // namespace fn_{}\n\n", i);
++i;
}
if (const char* dump_path = std::getenv("CUBCALL_DUMP_SOURCE"))
{
std::ofstream f(dump_path);
f << cuda_source;
}
if (std::getenv("CUBCALL_PRINT_SOURCE"))
{
std::fprintf(stderr,
"\n===== CubCall merged JIT source [%zu fns] =====\n%s\n===== end =====\n",
calls.size(),
cuda_source.c_str());
}
// Single Clang compile for the whole TU.
auto compiler = std::make_unique<JITCompiler>(jit_config);
if (!compiler->compile(cuda_source))
{
std::string err = compiler->getLastError();
bitcode.cleanup();
throw std::runtime_error("CubCall::compile (multi) compilation failed: " + err);
}
bitcode.cleanup();
// dlsym each function by its export name (positional order matches input).
using fn_t = int (*)(void*, ...);
std::vector<void*> fn_ptrs;
fn_ptrs.reserve(calls.size());
for (const auto& cb : calls)
{
auto fn = compiler->getFunction<fn_t>(cb.fn_name_);
if (!fn)
{
throw std::runtime_error("CubCall::compile (multi) function lookup failed: " + cb.fn_name_);
}
fn_ptrs.push_back(reinterpret_cast<void*>(fn));
}
auto cubin = compiler->getCubin();
return MultiCubCallResult{compiler.release(), std::move(cubin), std::move(fn_ptrs)};
}
} // namespace hostjit::codegen

View File

@@ -0,0 +1,288 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <algorithm>
#include <cstddef>
#include <format>
#include <hostjit/codegen/iterators.hpp>
#include <hostjit/codegen/types.hpp>
namespace hostjit::codegen
{
namespace
{
// The iterator struct holds a `long long _delta` lazy-offset field, so its
// natural alignment is at least alignof(long long)==8. C++ rejects alignas
// values smaller than the natural alignment; clamp here so user iterators with
// small `it.alignment` (e.g. 1 for a `char` state) still produce a valid struct.
inline std::size_t struct_alignas(std::size_t it_alignment)
{
const std::size_t base = it_alignment > 0 ? it_alignment : 1;
return base < alignof(long long) ? alignof(long long) : base;
}
} // namespace
IteratorCode make_input_iterator(
cccl_iterator_t it,
const std::string& value_type_name,
const std::string& accum_type_name,
const std::string& struct_name,
const std::string& var_name,
const std::string& state_param)
{
IteratorCode result;
result.local_var = var_name;
if (it.type == CCCL_POINTER)
{
// For pointer iterators, the element type is value_type.
// When value_type_name is empty (unknown/struct type), resolve it from the iterator's
// value_type info to get a correctly-sized storage struct — falling back to accum_t
// would use the wrong element size if the value type differs from the accumulator.
std::string elem_type;
if (value_type_name.empty())
{
auto elem_alias = struct_name + "_elem_t";
elem_type = resolve_type(it.value_type, elem_alias.c_str(), result.preamble);
}
else
{
elem_type = value_type_name;
}
result.type_name = elem_type + "*";
result.preamble += std::format("using {} = {}*;\n\n", struct_name, elem_type);
result.setup_code = std::format("{} {} = static_cast<{}>({}); ", struct_name, var_name, struct_name, state_param);
}
else
{
// Custom iterator with state + advance + dereference
const std::string adv_name = (it.advance.name && it.advance.name[0]) ? it.advance.name : (var_name + "_advance");
const std::string deref_name =
(it.dereference.name && it.dereference.name[0]) ? it.dereference.name : (var_name + "_dereference");
auto input_val_type = value_type_name.empty() ? accum_type_name : value_type_name;
auto val_alias = var_name + "_value_t";
result.type_name = struct_name;
result.preamble = std::format("using {} = {};\n", val_alias, input_val_type);
result.preamble += std::format(
R"cpp(extern "C" __device__ void {}(void* state, const void* offset);
extern "C" __device__ void {}(const void* state, {}* result);
)cpp",
adv_name,
deref_name,
val_alias);
// Positional args: {0}=struct_name, {1}=val_alias, {2}=it.size, {3}=adv_name, {4}=deref_name, {5}=it.alignment
//
// Arithmetic ops (+, +=, ++) are __host__ __device__ so CUB's host
// dispatch (which does `iter += n` etc.) compiles in the freestanding
// host pass. They accumulate into `_delta` rather than calling the
// device-only `advance` bitcode. `operator*` (device-only) applies the
// accumulated `_delta` to a copy of state via `advance`, then derefs.
// `alignas({5})` matches the iterator's declared state alignment so the
// user-supplied advance/dereference (which casts state as a pointer/etc.)
// sees properly-aligned memory.
result.preamble += std::format(
R"cpp(struct alignas({5}) {0} {{
using value_type = {1};
using difference_type = long long;
using pointer = {1}*;
using reference = {1};
using iterator_category = cuda::std::random_access_iterator_tag;
alignas({5}) char state[{2}];
long long _delta = 0;
__host__ __device__ {0} operator+(difference_type n) const {{
{0} copy = *this;
copy._delta += n;
return copy;
}}
__host__ __device__ {0}& operator+=(difference_type n) {{
_delta += n;
return *this;
}}
__host__ __device__ {0}& operator++() {{ return *this += 1; }}
__host__ __device__ {0} operator++(int) {{ {0} tmp = *this; ++(*this); return tmp; }}
__host__ __device__ difference_type operator-(const {0}&) const {{ return 0; }}
__device__ {1} operator*() const {{
{0} copy = *this;
if (copy._delta != 0) {{
long long offset = copy._delta;
{3}(copy.state, &offset);
}}
{1} result;
{4}(copy.state, &result);
return result;
}}
__device__ {1} operator[](difference_type n) const {{ return *(*this + n); }}
__host__ __device__ bool operator==(const {0}&) const {{ return false; }}
__host__ __device__ bool operator!=(const {0}&) const {{ return true; }}
}};
)cpp",
struct_name, // {0}
val_alias, // {1}
it.size, // {2}
adv_name, // {3}
deref_name, // {4}
struct_alignas(it.alignment)); // {5}
result.setup_code = std::format(
R"cpp({} {};
__builtin_memcpy({}.state, {}, {});)cpp",
struct_name,
var_name,
var_name,
state_param,
it.size);
}
return result;
}
IteratorCode make_output_iterator(
cccl_iterator_t it,
const std::string& accum_type_name,
const std::string& struct_name,
const std::string& var_name,
const std::string& state_param,
const std::string& value_type_name)
{
IteratorCode result;
result.local_var = var_name;
// For custom iterators the element type comes from the dereference function so the
// accum_t fallback is fine; for pointer iterators we resolve the actual value_type
// below to get the correct element size.
const std::string elem_type = value_type_name.empty() ? accum_type_name : value_type_name;
if (it.type == CCCL_POINTER)
{
// When value_type_name is empty (unknown/struct type), resolve from the iterator's own
// value_type info so the element size is correct — not from accum_t which may differ.
std::string ptr_elem_type;
if (value_type_name.empty())
{
auto elem_alias = struct_name + "_elem_t";
ptr_elem_type = resolve_type(it.value_type, elem_alias.c_str(), result.preamble);
}
else
{
ptr_elem_type = value_type_name;
}
result.type_name = ptr_elem_type + "*";
result.preamble += std::format("using {} = {}*;\n\n", struct_name, ptr_elem_type);
result.setup_code = std::format("{} {} = static_cast<{}*>({});", struct_name, var_name, ptr_elem_type, state_param);
}
else
{
const std::string adv_name = (it.advance.name && it.advance.name[0]) ? it.advance.name : (var_name + "_advance");
const std::string deref_name =
(it.dereference.name && it.dereference.name[0]) ? it.dereference.name : (var_name + "_dereference");
auto proxy_name = var_name + "_proxy_t";
result.type_name = struct_name;
result.preamble = std::format(
R"cpp(extern "C" __device__ void {}(void* state, const void* offset);
extern "C" __device__ void {}(void* state, const void* value);
)cpp",
adv_name,
deref_name);
// The proxy carries a COPY of the iterator state, not a pointer to it.
// This is critical for indexed writes (output_it[i] = val): operator[] creates
// a temporary advanced iterator, calls operator* on it, and returns the proxy
// by value. After operator[] returns the temporary is destroyed, so a pointer
// to its state would be dangling. Storing the state bytes in the proxy itself
// makes the proxy self-contained and safe across that return.
// Proxy contains only `char state[N]` so its natural alignment is 1; the
// struct alignas is the bigger of the iterator's declared alignment and 1.
const std::size_t proxy_align = it.alignment > 0 ? it.alignment : 1;
result.preamble += std::format(
R"cpp(struct alignas({1}) {0} {{
alignas({1}) char state[{2}];
__device__ void operator=(const {3}& val) {{
{4}(state, &val);
}}
}};
)cpp",
proxy_name, // {0}
proxy_align, // {1}
it.size, // {2}
elem_type, // {3}
deref_name); // {4}
// Arithmetic ops (+, +=, ++) are __host__ __device__ so CUB's host
// dispatch compiles; they accumulate `_delta` instead of calling the
// device-only `advance` bitcode. operator* (device only) applies the
// accumulated `_delta` before constructing the proxy.
result.preamble += std::format(
R"cpp(struct alignas({5}) {0} {{
using value_type = {1};
using difference_type = long long;
using pointer = {1}*;
using reference = {2};
using iterator_category = cuda::std::random_access_iterator_tag;
alignas({5}) char state[{3}];
long long _delta = 0;
__host__ __device__ {0} operator+(difference_type n) const {{
{0} copy = *this;
copy._delta += n;
return copy;
}}
__host__ __device__ {0}& operator+=(difference_type n) {{
_delta += n;
return *this;
}}
__host__ __device__ {0}& operator++() {{ return *this += 1; }}
__host__ __device__ {0} operator++(int) {{ {0} tmp = *this; ++(*this); return tmp; }}
__host__ __device__ difference_type operator-(const {0}&) const {{ return 0; }}
__device__ reference operator*() const {{
{2} proxy;
__builtin_memcpy(proxy.state, state, {3});
if (_delta != 0) {{
long long offset = _delta;
{4}(proxy.state, &offset);
}}
return proxy;
}}
__device__ reference operator[](difference_type n) const {{ return *(*this + n); }}
}};
)cpp",
struct_name, // {0}
elem_type, // {1}
proxy_name, // {2}
it.size, // {3}
adv_name, // {4}
struct_alignas(it.alignment)); // {5}
result.setup_code = std::format(
R"cpp({} {};
__builtin_memcpy({}.state, {}, {});)cpp",
struct_name,
var_name,
var_name,
state_param,
it.size);
}
return result;
}
} // namespace hostjit::codegen

View File

@@ -0,0 +1,613 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <format>
#include <hostjit/codegen/operators.hpp>
namespace hostjit::codegen
{
namespace
{
std::string generate_op_source(cccl_op_t op, bool has_bitcode, bool is_stateful)
{
const std::string op_name = (op.name && op.name[0]) ? op.name : "user_op";
std::string src;
if (op.code_type == CCCL_OP_CPP_SOURCE && op.code && op.code_size > 0)
{
// Embed C++ source directly
src += std::string(op.code, op.code_size) + "\n\n";
}
else if (has_bitcode)
{
// Extern declaration for bitcode-linked operation
if (is_stateful)
{
src += std::format("extern \"C\" __device__ void {}(void* state, void* a_ptr, void* b_ptr, void* out_ptr);\n\n",
op_name);
}
else
{
src += std::format("extern \"C\" __device__ void {}(void* a_ptr, void* b_ptr, void* out_ptr);\n\n", op_name);
}
}
return src;
}
std::string generate_binary_functor(cccl_op_t op, const std::string& accum_type, const std::string& functor_name)
{
const std::string op_name = (op.name && op.name[0]) ? op.name : "user_op";
const bool is_stateful = (op.type == CCCL_STATEFUL);
// Templated operator() lets CUB instantiate the functor with whatever
// element types its kernel deduces (important for binary transform with
// two differently-typed input iterators). The user's bitcode hop takes
// void* anyway, so the concrete arg types only need to be addressable.
if (is_stateful)
{
// Embed the user's state bytes inline. When CUB launches a kernel with
// this functor by value, the bytes ride along in the launch-arg buffer
// into device constant memory, so the address handed to the user's op
// (`state_bytes`) is a valid device-side pointer. Storing a host pointer
// here would crash on first device-side dereference.
const size_t state_size = op.size > 0 ? op.size : 1;
const size_t state_align = op.alignment > 0 ? op.alignment : 1;
return std::format(
R"cpp(struct {0} {{
alignas({3}) unsigned char state_bytes[{4}];
template <typename _A, typename _B>
__host__ __device__ __forceinline__
{1} operator()(const _A& a, const _B& b) const {{
{1} result;
{2}((void*)state_bytes, (void*)&a, (void*)&b, (void*)&result);
return result;
}}
}};
)cpp",
functor_name,
accum_type,
op_name,
state_align,
state_size);
}
else
{
return std::format(
R"cpp(struct {0} {{
template <typename _A, typename _B>
__host__ __device__ __forceinline__
{1} operator()(const _A& a, const _B& b) const {{
{1} result;
{2}((void*)&a, (void*)&b, (void*)&result);
return result;
}}
}};
)cpp",
functor_name,
accum_type,
op_name);
}
}
std::string generate_comparison_functor(cccl_op_t op, const std::string& key_type, const std::string& functor_name)
{
const std::string op_name = (op.name && op.name[0]) ? op.name : "user_op";
const bool is_stateful = (op.type == CCCL_STATEFUL);
if (is_stateful)
{
// See generate_binary_functor: state must travel by value via kernel-arg
// copy, not by host pointer, or the device-side deref crashes.
const size_t state_size = op.size > 0 ? op.size : 1;
const size_t state_align = op.alignment > 0 ? op.alignment : 1;
return std::format(
R"cpp(struct {0} {{
alignas({3}) unsigned char state_bytes[{4}];
__host__ __device__ __forceinline__
bool operator()(const {1}& a, const {2}& b) const {{
bool result;
{5}((void*)state_bytes, (void*)&a, (void*)&b, (void*)&result);
return result;
}}
}};
)cpp",
functor_name,
key_type,
key_type,
state_align,
state_size,
op_name);
}
else
{
return std::format(
R"cpp(struct {} {{
__host__ __device__ __forceinline__
bool operator()(const {}& a, const {}& b) const {{
bool result;
{}((void*)&a, (void*)&b, (void*)&result);
return result;
}}
}};
)cpp",
functor_name,
key_type,
key_type,
op_name);
}
}
// Returns the cuda::std (or cuda::) functor type string for a well-known binary op, or nullptr if not well-known.
const char* get_well_known_binary_functor_type(cccl_op_kind_t kind)
{
switch (kind)
{
case CCCL_PLUS:
return "::cuda::std::plus<>";
case CCCL_MINUS:
return "::cuda::std::minus<>";
case CCCL_MULTIPLIES:
return "::cuda::std::multiplies<>";
case CCCL_DIVIDES:
return "::cuda::std::divides<>";
case CCCL_MODULUS:
return "::cuda::std::modulus<>";
case CCCL_EQUAL_TO:
return "::cuda::std::equal_to<>";
case CCCL_NOT_EQUAL_TO:
return "::cuda::std::not_equal_to<>";
case CCCL_GREATER:
return "::cuda::std::greater<>";
case CCCL_LESS:
return "::cuda::std::less<>";
case CCCL_GREATER_EQUAL:
return "::cuda::std::greater_equal<>";
case CCCL_LESS_EQUAL:
return "::cuda::std::less_equal<>";
case CCCL_LOGICAL_AND:
return "::cuda::std::logical_and<>";
case CCCL_LOGICAL_OR:
return "::cuda::std::logical_or<>";
case CCCL_BIT_AND:
return "::cuda::std::bit_and<>";
case CCCL_BIT_OR:
return "::cuda::std::bit_or<>";
case CCCL_BIT_XOR:
return "::cuda::std::bit_xor<>";
case CCCL_MINIMUM:
return "::cuda::minimum<>";
case CCCL_MAXIMUM:
return "::cuda::maximum<>";
default:
return nullptr;
}
}
// Returns the cuda::std functor type string for a well-known unary op, or nullptr if not well-known.
const char* get_well_known_unary_functor_type(cccl_op_kind_t kind)
{
switch (kind)
{
case CCCL_LOGICAL_NOT:
return "::cuda::std::logical_not<>";
case CCCL_BIT_NOT:
return "::cuda::std::bit_not<>";
case CCCL_IDENTITY:
return "::cuda::std::identity";
case CCCL_NEGATE:
return "::cuda::std::negate<>";
default:
return nullptr;
}
}
// Returns the C++ operator symbol for a well-known op, or nullptr if none.
const char* get_well_known_op_symbol(cccl_op_kind_t kind)
{
switch (kind)
{
case CCCL_PLUS:
return "+";
case CCCL_MINUS:
return "-";
case CCCL_MULTIPLIES:
return "*";
case CCCL_DIVIDES:
return "/";
case CCCL_MODULUS:
return "%";
case CCCL_EQUAL_TO:
return "==";
case CCCL_NOT_EQUAL_TO:
return "!=";
case CCCL_GREATER:
return ">";
case CCCL_LESS:
return "<";
case CCCL_GREATER_EQUAL:
return ">=";
case CCCL_LESS_EQUAL:
return "<=";
case CCCL_LOGICAL_AND:
return "&&";
case CCCL_LOGICAL_OR:
return "||";
case CCCL_LOGICAL_NOT:
return "!";
case CCCL_BIT_AND:
return "&";
case CCCL_BIT_OR:
return "|";
case CCCL_BIT_XOR:
return "^";
case CCCL_BIT_NOT:
return "~";
case CCCL_NEGATE:
return "-";
default:
return nullptr;
}
}
// Generate preamble for a well-known binary op.
// For custom types with user-provided code, declares the extern "C" function
// and generates an operator overload that calls it.
// For primitive types without user code, no preamble is needed.
std::string
generate_well_known_preamble(cccl_op_t op, const std::string& accum_type, bool has_bitcode, bool is_comparison)
{
const std::string op_name = (op.name && op.name[0]) ? op.name : "user_op";
const std::string return_type = is_comparison ? "bool" : accum_type;
const char* symbol = get_well_known_op_symbol(op.type);
bool has_user_code = has_bitcode || (op.code_type == CCCL_OP_CPP_SOURCE && op.code && op.code_size > 0);
if (!has_user_code)
{
// Pure well-known op on a primitive type — no preamble needed.
return "";
}
std::string src;
if (op.code_type == CCCL_OP_CPP_SOURCE && op.code && op.code_size > 0)
{
// Embed C++ source directly (may contain type definitions).
src += std::string(op.code, op.code_size) + "\n\n";
}
// Declare the extern "C" function from bitcode.
if (has_bitcode)
{
src += std::format("extern \"C\" __device__ void {}(void* a_ptr, void* b_ptr, void* out_ptr);\n\n", op_name);
}
// Generate an operator overload that calls the user-provided function,
// so cuda::std::plus<> (etc.) can use it on custom types.
if (symbol)
{
src += std::format(
R"cpp(__device__ {0} operator{1}(const {2}& lhs, const {2}& rhs) {{
{0} ret;
{3}((void*)&lhs, (void*)&rhs, (void*)&ret);
return ret;
}}
)cpp",
return_type,
symbol,
accum_type,
op_name);
}
return src;
}
// Generate preamble for a well-known unary op with user-provided code.
// The operator overload lets the cuda::std functor invoke that code for a
// custom type. Primitive types without user code need no preamble.
std::string generate_well_known_unary_preamble(
cccl_op_t op, const std::string& in_type, const std::string& out_type, bool has_bitcode)
{
const std::string op_name = (op.name && op.name[0]) ? op.name : "user_op";
const char* symbol = get_well_known_op_symbol(op.type);
bool has_user_code = has_bitcode || (op.code_type == CCCL_OP_CPP_SOURCE && op.code && op.code_size > 0);
if (!has_user_code)
{
return "";
}
std::string src;
if (op.code_type == CCCL_OP_CPP_SOURCE && op.code && op.code_size > 0)
{
src += std::string(op.code, op.code_size) + "\n\n";
}
if (has_bitcode)
{
src += std::format("extern \"C\" __device__ void {}(void* a_ptr, void* out_ptr);\n\n", op_name);
}
if (symbol)
{
src += std::format(
R"cpp(__device__ {0} operator{1}(const {2}& value) {{
{0} ret;
{3}((void*)&value, (void*)&ret);
return ret;
}}
)cpp",
out_type,
symbol,
in_type,
op_name);
}
return src;
}
} // anonymous namespace
OperatorCode make_binary_op(
cccl_op_t op,
const std::string& accum_type,
const std::string& functor_name,
const std::string& var_name,
const std::string& state_param,
bool has_bitcode)
{
// For well-known operations, use cuda::std functors directly.
// For custom types, generate an operator overload that wraps the user-provided function.
// If the caller provided bitcode, prefer it: the well-known functor (e.g.
// cuda::std::plus<void>) may not be invocable on the custom value type.
const char* well_known_type = get_well_known_binary_functor_type(op.type);
if (well_known_type && !has_bitcode)
{
OperatorCode result;
result.local_var = var_name;
result.preamble = generate_well_known_preamble(op, accum_type, has_bitcode, /*is_comparison=*/false);
result.setup_code = std::format("{} {}{{}};", well_known_type, var_name);
return result;
}
const bool is_stateful = (op.type == CCCL_STATEFUL);
OperatorCode result;
result.local_var = var_name;
result.preamble = generate_op_source(op, has_bitcode, is_stateful);
result.preamble += generate_binary_functor(op, accum_type, functor_name);
if (is_stateful)
{
const size_t state_size = op.size > 0 ? op.size : 1;
result.setup_code = std::format(
"{0} {1}; __builtin_memcpy({1}.state_bytes, {2}, {3});", functor_name, var_name, state_param, state_size);
}
else
{
result.setup_code = std::format("{} {};", functor_name, var_name);
}
return result;
}
OperatorCode make_unary_op(
cccl_op_t op,
const std::string& in_type,
const std::string& out_type,
const std::string& functor_name,
const std::string& var_name,
const std::string& state_param,
bool has_bitcode)
{
// Well-known operations map directly to cuda::std unary functors. If the
// caller provided bitcode, prefer it because the functor may not be
// invocable on the user's custom value type.
const char* well_known_type = get_well_known_unary_functor_type(op.type);
if (well_known_type && !has_bitcode)
{
OperatorCode result;
result.local_var = var_name;
result.preamble = generate_well_known_unary_preamble(op, in_type, out_type, has_bitcode);
result.setup_code = std::format("{} {}{{}};", well_known_type, var_name);
return result;
}
const bool is_stateful = (op.type == CCCL_STATEFUL);
const std::string op_name = (op.name && op.name[0]) ? op.name : "user_op";
OperatorCode result;
result.local_var = var_name;
// Preamble: extern decl or embedded C++ source
if (op.code_type == CCCL_OP_CPP_SOURCE && op.code && op.code_size > 0)
{
result.preamble += std::string(op.code, op.code_size) + "\n\n";
}
else if (has_bitcode)
{
if (is_stateful)
{
result.preamble +=
std::format("extern \"C\" __device__ void {}(void* state, void* a_ptr, void* result_ptr);\n\n", op_name);
}
else
{
result.preamble += std::format("extern \"C\" __device__ void {}(void* a_ptr, void* result_ptr);\n\n", op_name);
}
}
// Functor struct
if (is_stateful)
{
// See generate_binary_functor: state must travel by value via kernel-arg
// copy, not by host pointer, or the device-side deref crashes.
const size_t state_size = op.size > 0 ? op.size : 1;
const size_t state_align = op.alignment > 0 ? op.alignment : 1;
result.preamble += std::format(
R"cpp(struct {0} {{
alignas({4}) unsigned char state_bytes[{5}];
__host__ __device__ __forceinline__
{1} operator()(const {2}& a) const {{
{3} result;
{6}((void*)state_bytes, (void*)&a, (void*)&result);
return result;
}}
}};
)cpp",
functor_name,
out_type,
in_type,
out_type,
state_align,
state_size,
op_name);
result.setup_code = std::format(
"{0} {1}; __builtin_memcpy({1}.state_bytes, {2}, {3});", functor_name, var_name, state_param, state_size);
}
else
{
result.preamble += std::format(
R"cpp(struct {} {{
__host__ __device__ __forceinline__
{} operator()(const {}& a) const {{
{} result;
{}((void*)&a, (void*)&result);
return result;
}}
}};
)cpp",
functor_name,
out_type,
in_type,
out_type,
op_name);
result.setup_code = std::format("{} {};", functor_name, var_name);
}
return result;
}
OperatorCode make_comparison_op(
cccl_op_t op,
const std::string& key_type,
const std::string& functor_name,
const std::string& var_name,
const std::string& state_param,
bool has_bitcode)
{
const char* well_known_type = get_well_known_binary_functor_type(op.type);
if (well_known_type && !has_bitcode)
{
OperatorCode result;
result.local_var = var_name;
result.preamble = generate_well_known_preamble(op, key_type, has_bitcode, /*is_comparison=*/true);
result.setup_code = std::format("{} {}{{}};", well_known_type, var_name);
return result;
}
const bool is_stateful = (op.type == CCCL_STATEFUL);
OperatorCode result;
result.local_var = var_name;
result.preamble = generate_op_source(op, has_bitcode, is_stateful);
result.preamble += generate_comparison_functor(op, key_type, functor_name);
if (is_stateful)
{
const size_t state_size = op.size > 0 ? op.size : 1;
result.setup_code = std::format(
"{0} {1}; __builtin_memcpy({1}.state_bytes, {2}, {3});", functor_name, var_name, state_param, state_size);
}
else
{
result.setup_code = std::format("{} {};", functor_name, var_name);
}
return result;
}
OperatorCode make_for_each_op(
cccl_op_t op,
const std::string& elem_type,
const std::string& functor_name,
const std::string& var_name,
const std::string& state_param,
bool has_bitcode)
{
const std::string op_name = (op.name && op.name[0]) ? op.name : "user_op";
const bool is_stateful = (op.type == CCCL_STATEFUL);
OperatorCode result;
result.local_var = var_name;
// Forward declaration / embedded source.
if (op.code_type == CCCL_OP_CPP_SOURCE && op.code && op.code_size > 0)
{
result.preamble += std::string(op.code, op.code_size) + "\n\n";
}
else if (has_bitcode)
{
if (is_stateful)
{
result.preamble +=
std::format("extern \"C\" __device__ void {}(void* state, {}* input);\n\n", op_name, elem_type);
}
else
{
result.preamble += std::format("extern \"C\" __device__ void {}({}* input);\n\n", op_name, elem_type);
}
}
if (is_stateful)
{
const size_t state_size = op.size > 0 ? op.size : 1;
const size_t state_align = op.alignment > 0 ? op.alignment : 1;
result.preamble += std::format(
R"cpp(struct {0} {{
alignas({3}) unsigned char state_bytes[{4}];
__device__ __forceinline__ void operator()({1}& elem) const {{ {2}((void*)state_bytes, &elem); }}
}};
)cpp",
functor_name,
elem_type,
op_name,
state_align,
state_size);
result.setup_code = std::format(
"{0} {1}; __builtin_memcpy({1}.state_bytes, {2}, {3});", functor_name, var_name, state_param, state_size);
}
else
{
result.preamble += std::format(
R"cpp(struct {0} {{
__device__ __forceinline__ void operator()({1}& elem) const {{ {2}(&elem); }}
}};
)cpp",
functor_name,
elem_type,
op_name);
result.setup_code = std::format("{} {}{{}};", functor_name, var_name);
}
return result;
}
} // namespace hostjit::codegen

View File

@@ -0,0 +1,73 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <format>
#include <hostjit/codegen/types.hpp>
namespace hostjit::codegen
{
std::string get_type_name(cccl_type_enum type)
{
switch (type)
{
case CCCL_INT8:
return "char";
case CCCL_INT16:
return "short";
case CCCL_INT32:
return "int";
case CCCL_INT64:
return "long long";
case CCCL_UINT8:
return "unsigned char";
case CCCL_UINT16:
return "unsigned short";
case CCCL_UINT32:
return "unsigned int";
case CCCL_UINT64:
return "unsigned long long";
case CCCL_FLOAT16:
return "__half";
case CCCL_FLOAT32:
return "float";
case CCCL_FLOAT64:
return "double";
case CCCL_BOOLEAN:
return "bool";
default:
return "";
}
}
std::string make_storage_type(const char* name, size_t size, size_t alignment)
{
return std::format(
R"cpp(struct __align__({}) {} {{
char data[{}];
}};
)cpp",
alignment,
name,
size);
}
std::string resolve_type(cccl_type_info info, const char* fallback_alias, std::string& out_preamble)
{
auto name = get_type_name(info.type);
if (!name.empty())
{
return name;
}
// Custom type: emit storage struct definition, return alias
out_preamble += make_storage_type(fallback_alias, info.size, info.alignment);
return fallback_alias;
}
} // namespace hostjit::codegen

File diff suppressed because it is too large Load Diff

View File

@@ -0,0 +1,175 @@
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <cuda_runtime.h>
#include <hostjit/config.hpp>
namespace hostjit
{
CompilerConfig detectDefaultConfig()
{
CompilerConfig config;
// Detect CUDA toolkit path
if (const char* env = std::getenv("CUDA_PATH"))
{
config.cuda_toolkit_path = env;
}
else if (const char* env = std::getenv("CUDA_HOME"))
{
config.cuda_toolkit_path = env;
}
#ifdef CUDA_TOOLKIT_PATH
else
{
config.cuda_toolkit_path = CUDA_TOOLKIT_PATH;
}
#endif
// Set up library paths if CUDA toolkit was found
if (!config.cuda_toolkit_path.empty())
{
std::filesystem::path lib64_path = std::filesystem::path(config.cuda_toolkit_path) / "lib64";
std::filesystem::path lib_path = std::filesystem::path(config.cuda_toolkit_path) / "lib";
if (std::filesystem::exists(lib64_path))
{
config.library_paths.push_back(lib64_path.string());
}
else if (std::filesystem::exists(lib_path))
{
config.library_paths.push_back(lib_path.string());
}
}
// Auto-detect GPU compute capability using CUDA runtime
int device = 0;
if (cudaGetDevice(&device) == cudaSuccess)
{
cudaDeviceProp prop;
if (cudaGetDeviceProperties(&prop, device) == cudaSuccess)
{
int detected_sm = prop.major * 10 + prop.minor;
if (detected_sm >= 75)
{
config.sm_version = detected_sm;
}
}
}
if (config.sm_version == 0)
{
config.sm_version = 75;
}
config.optimization_level = 2;
config.debug = false;
config.verbose = false;
// Detect hostjit include path
if (const char* env = std::getenv("HOSTJIT_INCLUDE_PATH"))
{
config.hostjit_include_path = env;
}
#ifdef HOSTJIT_INCLUDE_DIR
else
{
config.hostjit_include_path = HOSTJIT_INCLUDE_DIR;
}
#endif
// Detect clang headers path. Build-time CLANG_HEADERS_DIR is the default;
// HOSTJIT_CLANG_PATH overrides it (e.g. for pip-installed wheels with a
// packaged copy of clang's CUDA headers).
if (const char* env = std::getenv("HOSTJIT_CLANG_PATH"))
{
config.clang_headers_path = env;
}
#ifdef CLANG_HEADERS_DIR
else
{
config.clang_headers_path = CLANG_HEADERS_DIR;
}
#endif
return config;
}
bool validateConfig(const CompilerConfig& config, std::string* error_message)
{
if (config.cuda_toolkit_path.empty())
{
if (error_message)
{
*error_message = "CUDA toolkit path not found. Please set CUDA_PATH or CUDA_HOME environment variable.";
}
return false;
}
if (!std::filesystem::exists(config.cuda_toolkit_path))
{
if (error_message)
{
*error_message = "CUDA toolkit path does not exist: " + config.cuda_toolkit_path;
}
return false;
}
std::filesystem::path cuda_h = std::filesystem::path(config.cuda_toolkit_path) / "include" / "cuda.h";
if (!std::filesystem::exists(cuda_h))
{
if (error_message)
{
*error_message = "CUDA headers not found at: " + cuda_h.string();
}
return false;
}
for (const auto& include_path : config.include_paths)
{
if (!std::filesystem::exists(include_path))
{
if (error_message)
{
*error_message = "Include path does not exist: " + include_path;
}
return false;
}
}
for (const auto& library_path : config.library_paths)
{
if (!std::filesystem::exists(library_path))
{
if (error_message)
{
*error_message = "Library path does not exist: " + library_path;
}
return false;
}
}
if (config.sm_version < 30 || config.sm_version > 150)
{
if (error_message)
{
*error_message = "Invalid SM version: " + std::to_string(config.sm_version) + " (must be between 30 and 150)";
}
return false;
}
if (config.optimization_level < 0 || config.optimization_level > 3)
{
if (error_message)
{
*error_message =
"Invalid optimization level: " + std::to_string(config.optimization_level) + " (must be between 0 and 3)";
}
return false;
}
return true;
}
} // namespace hostjit

View File

@@ -0,0 +1,56 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <cstdint>
#include <set>
#include <string>
#include <unordered_set>
#include <vector>
#include <cccl/c/types.h>
#include <hostjit/config.hpp>
namespace hostjit::codegen
{
// Manages bitcode files needed for linking. Collects LTOIR, LLVM IR,
// and C++ source (compiling the latter to bitcode on the fly).
// Tracks temp file paths for cleanup.
class BitcodeCollector
{
public:
explicit BitcodeCollector(CompilerConfig& config, uintptr_t unique_id);
// Add bitcode from an operator (handles LTOIR, LLVM_IR, CPP_SOURCE,
// and extra modules).
void add_op(cccl_op_t op, const std::string& label);
// Add bitcode from a custom iterator's advance/dereference ops.
void add_iterator(cccl_iterator_t it, const std::string& label_prefix);
// Returns true if the op has linked bitcode (LTOIR or LLVM_IR).
static bool is_bitcode_op(cccl_op_t op);
// Clean up all temporary files.
void cleanup();
private:
void add_raw_bitcode(const char* data, size_t size, const std::string& name);
bool compile_and_add(const char* source, size_t source_size, const std::string& name);
void add_op_code(cccl_op_t& op, const std::string& name);
CompilerConfig& config_;
uintptr_t unique_id_;
std::vector<std::string> temp_paths_;
std::set<std::string> added_symbols_; // dedup by op.name (when present)
std::unordered_set<std::size_t> added_content_hashes_; // dedup by content hash for unnamed extras
};
} // namespace hostjit::codegen

View File

@@ -0,0 +1,304 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <string>
#include <variant>
#include <vector>
#include <cccl/c/types.h>
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
namespace hostjit::codegen
{
// Tags for non-cccl arguments (no runtime data, just control code generation)
struct temp_storage_t
{};
struct temp_bytes_t
{};
// num_items_t carries a name so the same tag type can express num_segments,
// num_needles, etc. — each becomes its own unsigned long long parameter.
struct num_items_t
{
const char* name = "num_items";
};
struct stream_t
{};
inline constexpr temp_storage_t temp_storage{};
inline constexpr temp_bytes_t temp_bytes{};
inline constexpr num_items_t num_items{};
inline constexpr num_items_t num_segments{"num_segments"};
inline constexpr num_items_t num_needles{"num_needles"};
inline constexpr num_items_t num_haystack{"num_haystack"};
inline constexpr stream_t stream{};
// Direction wrappers for iterators (cccl_iterator_t doesn't encode direction)
struct input_t
{
cccl_iterator_t it;
};
struct output_t
{
cccl_iterator_t it;
};
inline input_t in(cccl_iterator_t it)
{
return {it};
}
inline output_t out(cccl_iterator_t it)
{
return {it};
}
// cmp_t: wraps a cccl_op_t that should generate a comparison functor
// (bool operator()(const T&, const T&)) rather than the default binary reduce
// functor (T operator()(T, T)). Use cmp(op) where sort/search operators go.
struct cmp_t
{
cccl_op_t op;
};
inline cmp_t cmp(cccl_op_t op)
{
return {op};
}
// future_val_t: the init value lives on the device at runtime. Generates
// cub::FutureValue<accum_t>(static_cast<accum_t*>(param)) in the CUB call.
// Carries type info so find_accum_type can resolve accum_t correctly.
struct future_val_t
{
cccl_type_info type;
};
inline future_val_t future_val(cccl_type_info t)
{
return {t};
}
// unary_op_t: wraps a cccl_op_t used as a unary transform operator (T -> U).
// Carries the input/output type info so the functor can be typed correctly.
struct unary_op_t
{
cccl_op_t op;
cccl_type_info in_type;
cccl_type_info out_type;
};
inline unary_op_t unary_op(cccl_op_t op, cccl_type_info in_t, cccl_type_info out_t)
{
return {op, in_t, out_t};
}
// force_accum_type_t: overrides the accumulator type resolved by find_accum_type.
// Use when the natural accum type (first input) differs from the desired type.
// Generates no code — only influences type resolution.
struct force_accum_type_t
{
cccl_type_info type;
};
inline force_accum_type_t force_accum_type(cccl_type_info t)
{
return {t};
}
// pred(): shorthand for a unary bool predicate operator (e.g. for partition).
// Equivalent to unary_op with out_type = bool.
// Generates: bool operator()(const item_t& a) const { ... }
inline unary_op_t pred(cccl_op_t op, cccl_type_info item_t)
{
return {op, item_t, cccl_type_info{sizeof(bool), alignof(bool), CCCL_BOOLEAN}};
}
// typed_scalar_t: a by-value scalar of any cccl-known type, passed into the
// JIT wrapper as a host pointer and memcpy'd onto the stack before the CUB
// call. Use when the CUB API takes a small POD by value (e.g. radix_sort's
// `int begin_bit`, histogram's `int num_levels` / `level_t lower_level`).
// The caller supplies a void* host pointer to the value at the corresponding
// run-time arg position.
struct typed_scalar_t
{
cccl_type_info type;
const char* name;
};
inline typed_scalar_t typed_scalar(cccl_type_info t, const char* name)
{
return {t, name};
}
// env_stream_t: variant of stream_t that emits a cuda::std::execution::env
// wrapping a cuda::stream_ref instead of a bare cudaStream_t. Use with CUB
// algorithms that take an env (so CUB manages temp storage internally via
// the env's memory_resource — caller doesn't have to thread it through).
struct env_stream_t
{};
inline constexpr env_stream_t env_stream{};
// for_each_op_t: wraps a cccl_op_t with c.parallel's void op(T*) contract
// into the void op(T&) functor that cub::DeviceFor::ForEachN expects.
struct for_each_op_t
{
cccl_op_t op;
};
inline for_each_op_t for_each_op(cccl_op_t op)
{
return {op};
}
// double_buffer_t: constructs a cub::DoubleBuffer<elem_t> from two host
// pointers (one "in" buffer, one "out" buffer) and passes it to the CUB call.
// Used by the DoubleBuffer overloads of DeviceRadixSort / DeviceSegmentedSort
// where the caller is willing to let CUB swap buffers and report the final
// location via the buffer's `selector` member. var_name controls the C++ name
// of the generated local — pair a `selector_out_t` with the same name to read
// `<var_name>.selector` after the call.
struct double_buffer_t
{
cccl_iterator_t in_it;
cccl_iterator_t out_it;
const char* var_name;
};
inline double_buffer_t double_buffer(cccl_iterator_t in_it, cccl_iterator_t out_it, const char* var_name = "d_buffer")
{
return {in_it, out_it, var_name};
}
// selector_out_t: emits a `void* selector_out` parameter and, after the CUB
// call, writes `<buffer_var_name>.selector` to it. Must be paired with a
// double_buffer_t whose var_name matches.
struct selector_out_t
{
const char* buffer_var_name;
};
inline selector_out_t selector_out(const char* buffer_var_name = "d_buffer")
{
return {buffer_var_name};
}
// Argument variant: everything that can appear in .with()
using Arg = std::variant<
temp_storage_t,
temp_bytes_t,
num_items_t,
stream_t,
env_stream_t,
input_t,
output_t,
cccl_op_t,
cmp_t,
unary_op_t,
for_each_op_t,
double_buffer_t,
selector_out_t,
future_val_t,
cccl_value_t,
force_accum_type_t,
typed_scalar_t>;
// Result of a successful single-function compilation.
struct CubCallResult
{
JITCompiler* compiler; // caller takes ownership
void* fn_ptr; // the exported function
std::vector<char> cubin; // for SASS inspection
};
// Result of a successful multi-function compilation (one TU, N functions).
struct MultiCubCallResult
{
JITCompiler* compiler; // caller takes ownership; one compiler for the whole TU
std::vector<char> cubin; // single cubin for the whole TU
std::vector<void*> fn_ptrs; // exported functions in the same order as the input CubCalls
};
class CubCall
{
public:
// Start building: specify the CUB header to include.
static CubCall from(const char* include_header);
// Specify the CUB function to call (e.g., "cub::DeviceReduce::Reduce").
CubCall& run(const char* cub_function);
// Optionally override the exported function name (default: "cccl_jit_fn").
CubCall& name(const char* export_name);
// Add arguments in CUB call order. Each argument is dispatched by type.
template <typename... Args>
CubCall& with(Args&&... args)
{
(args_.emplace_back(Arg{std::forward<Args>(args)}), ...);
return *this;
}
// Wrap all input iterators in cuda::std::make_tuple() in the generated CUB call.
// Required for cub::DeviceTransform::Transform with multiple inputs.
CubCall& use_tuple_inputs()
{
tuple_inputs_ = true;
return *this;
}
// Generate the complete CUDA source string (useful for debugging).
std::string source() const;
// Compile the generated source and return the function pointer.
CubCallResult compile(
int cc_major,
int cc_minor,
cccl_build_config* config = nullptr,
const char* ctk_path = nullptr,
const char* cccl_include_path = nullptr) const;
// Compile multiple CubCalls into a single translation unit. One Clang
// invocation, one cubin, one JITCompiler; each function is dlsym'd by its
// .name(...) and returned in the input order. All CubCalls must share the
// same CUB include header (.from(...)). Per-function preambles are isolated
// inside `namespace fn_<i> { ... }` blocks; extern "C" symbols escape the
// namespace and stay globally dlsym-able.
static MultiCubCallResult compile(
std::initializer_list<CubCall> calls,
int cc_major,
int cc_minor,
cccl_build_config* config = nullptr,
const char* ctk_path = nullptr,
const char* cccl_include_path = nullptr);
private:
std::string include_;
std::string cub_function_;
std::string fn_name_ = "cccl_jit_fn";
std::vector<Arg> args_;
bool tuple_inputs_ = false;
// Internal: just the per-function body (preamble + function defn), no
// shared #includes. Used by the multi-compile path to wrap N bodies in
// N namespaces under a single shared include block.
std::string body() const;
// Internal: walk args_ and register any user-op / iterator bitcode with
// the given collector. Factored out so the multi-compile path can share
// one collector across several CubCalls.
void collect_bitcode(class BitcodeCollector& bitcode, int& op_idx, int& in_idx, int& out_idx) const;
// Internal: builds a hostjit::CompilerConfig from the standard cc + paths +
// build_config inputs every compile entry point takes. Shared between the
// single-fn and multi-fn compile overloads so flag/path handling lives in
// one place.
static hostjit::CompilerConfig make_jit_config(
int cc_major,
int cc_minor,
cccl_build_config* config,
const char* ctk_path,
const char* cccl_include_path,
const std::string& entry_point_name);
};
} // namespace hostjit::codegen

View File

@@ -0,0 +1,50 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <string>
#include <cccl/c/types.h>
namespace hostjit::codegen
{
// Result of generating iterator code.
struct IteratorCode
{
std::string preamble; // type alias or struct definition (goes at file scope)
std::string setup_code; // initialization inside function body
std::string local_var; // e.g., "in_0"
std::string type_name; // e.g., "in_0_it_t" or "accum_t*"
};
// Generate code for an input iterator.
// For CCCL_POINTER: emits a type alias and pointer cast.
// For CCCL_ITERATOR: emits a full iterator struct with advance/dereference.
IteratorCode make_input_iterator(
cccl_iterator_t it,
const std::string& value_type_name, // resolved C++ type of iterator's value
const std::string& accum_type_name, // accumulator type alias (for pointer fallback)
const std::string& struct_name, // e.g., "in_0_it_t"
const std::string& var_name, // e.g., "in_0"
const std::string& state_param); // e.g., "d_in_0" (void* param name)
// Generate code for an output iterator.
// value_type_name: if non-empty, overrides accum_type_name as the element type
// for the pointer/proxy. Use this when the output element type differs from the
// accumulator (e.g. item values in a key-value sort).
IteratorCode make_output_iterator(
cccl_iterator_t it,
const std::string& accum_type_name,
const std::string& struct_name,
const std::string& var_name,
const std::string& state_param,
const std::string& value_type_name = "");
} // namespace hostjit::codegen

View File

@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <string>
#include <cccl/c/types.h>
namespace hostjit::codegen
{
// Result of generating operator code.
struct OperatorCode
{
std::string preamble; // extern decl + functor struct (goes at file scope)
std::string setup_code; // initialization inside function body
std::string local_var; // e.g., "op_0"
};
// Generate code for a binary operator (reduce, scan).
// Produces an extern "C" device function declaration (or inline for well-known ops)
// and a functor struct that wraps it.
OperatorCode make_binary_op(
cccl_op_t op,
const std::string& accum_type, // C++ type name for operands
const std::string& functor_name, // e.g., "ReduceOp"
const std::string& var_name, // e.g., "op_0"
const std::string& state_param, // e.g., "op_0_state" (void* param name)
bool has_bitcode);
// Generate code for a unary operator (transform).
// Produces a functor with operator()(const in_type& a) const -> out_type.
OperatorCode make_unary_op(
cccl_op_t op,
const std::string& in_type, // C++ type name for input operand
const std::string& out_type, // C++ type name for result
const std::string& functor_name, // e.g., "UnaryOp"
const std::string& var_name, // e.g., "op_0"
const std::string& state_param, // e.g., "op_0_state" (void* param name)
bool has_bitcode);
// Generate code for a comparison operator (sort).
// Same as binary op but the functor returns bool.
OperatorCode make_comparison_op(
cccl_op_t op,
const std::string& key_type, // C++ type name for keys
const std::string& functor_name, // e.g., "CompareOp"
const std::string& var_name, // e.g., "cmp_0"
const std::string& state_param, // e.g., "cmp_0_state"
bool has_bitcode);
// Generate code for a for_each operator. Adapts c.parallel's user-op contract
// (`void op(T*)`) to the contract that cub::DeviceFor::ForEachN expects
// (`void op(T&)`). Functor is stateless for non-stateful ops; for stateful
// ops it embeds the state bytes inline so they ride along into device
// constant memory via the kernel-arg copy.
OperatorCode make_for_each_op(
cccl_op_t op,
const std::string& elem_type, // C++ type name for the iterator's element
const std::string& functor_name, // e.g., "ForEachOp"
const std::string& var_name, // e.g., "op_0"
const std::string& state_param, // e.g., "op_0_state"
bool has_bitcode);
} // namespace hostjit::codegen

View File

@@ -0,0 +1,32 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <string>
#include <cccl/c/types.h>
namespace hostjit::codegen
{
// Maps cccl_type_enum to plain C/C++ type names (e.g., "int", "float").
// Returns "" for CCCL_STORAGE (caller must handle custom types).
std::string get_type_name(cccl_type_enum type);
// Generates an aligned storage struct definition.
// Example: "struct __align__(8) my_storage_t {\n char data[16];\n};\n"
std::string make_storage_type(const char* name, size_t size, size_t alignment);
// Returns the C++ type name for a cccl_type_info.
// For known types, returns the type name directly.
// For CCCL_STORAGE, emits a storage struct definition into `out_preamble`
// and returns `fallback_alias`.
std::string resolve_type(cccl_type_info info, const char* fallback_alias, std::string& out_preamble);
} // namespace hostjit::codegen

View File

@@ -0,0 +1,54 @@
#pragma once
#include <string>
#include <vector>
namespace hostjit
{
struct CompilationResult
{
bool success;
std::string object_file_path; // Path to generated .o file
std::string diagnostics; // Compiler messages
std::vector<char> cubin; // Device cubin extracted during compilation
};
struct BitcodeResult
{
bool success;
std::string bitcode; // LLVM bitcode bytes
std::string diagnostics;
};
struct LinkResult
{
bool success;
std::string library_path; // Path to .so file
std::string diagnostics;
};
// Forward declaration to avoid including heavy Clang headers
struct CompilerConfig;
class CUDACompiler
{
public:
CUDACompiler();
~CUDACompiler();
// Compile CUDA device source to LLVM bitcode
BitcodeResult compileToDeviceBitcode(const std::string& source_code, const CompilerConfig& config);
// Compile CUDA source code to object file
CompilationResult
compileToObject(const std::string& source_code, const std::string& output_path, const CompilerConfig& config);
// Link object files to shared library
LinkResult linkToSharedLibrary(
const std::vector<std::string>& object_files, const std::string& output_path, const CompilerConfig& config);
private:
class Impl;
Impl* impl_;
};
} // namespace hostjit

View File

@@ -0,0 +1,36 @@
#pragma once
#include <string>
#include <unordered_map>
#include <vector>
namespace hostjit
{
struct CompilerConfig
{
std::string cuda_toolkit_path;
std::string hostjit_include_path; // Path to hostjit include directory (for minimal CUDA runtime)
std::string clang_headers_path; // Path to Clang's built-in CUDA headers (overrides CLANG_HEADERS_DIR)
std::string cccl_include_path; // Path to CCCL headers (overrides CCCL_SOURCE_DIR); contains cub/, thrust/, cuda/
std::vector<std::string> include_paths;
std::vector<std::string> library_paths;
std::vector<std::string> device_bitcode_files; // Raw LLVM bitcode (magic "BC") linked via LLVM's Linker
std::vector<std::string> device_ltoir_files; // NVRTC LTOIR; linked at the nvJitLink stage with -lto
std::unordered_map<std::string, std::string> macro_definitions; // key=macro name, value=macro value (empty for flag
// macros)
int sm_version = 70;
int optimization_level = 2;
bool debug = false;
bool verbose = false;
bool trace_includes = false; // Show all included headers during compilation (for debugging header search)
bool keep_artifacts = false; // Keep compiled artifacts for inspection (PTX, object files, etc.)
std::string entry_point_name; // Name of the exported entry point function (used for post-link optimization)
bool enable_pch = false; // Cache precompiled headers on disk to speed up repeated builds
};
// Auto-detect CUDA toolkit and create default configuration
CompilerConfig detectDefaultConfig();
// Validate that the configuration is usable
bool validateConfig(const CompilerConfig& config, std::string* error_message = nullptr);
} // namespace hostjit

View File

@@ -0,0 +1,371 @@
/*===-- __clang_cuda_libdevice_declares.h - decls for libdevice functions --===
*
* Part of the LLVM Project, 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
*
*===-----------------------------------------------------------------------===
*/
#ifndef __CLANG_CUDA_LIBDEVICE_DECLARES_H__
#define __CLANG_CUDA_LIBDEVICE_DECLARES_H__
#if defined(__cplusplus)
extern "C" {
#endif
#define __DEVICE__ __device__
__DEVICE__ int __nv_abs(int __a);
__DEVICE__ double __nv_acos(double __a);
__DEVICE__ float __nv_acosf(float __a);
__DEVICE__ double __nv_acosh(double __a);
__DEVICE__ float __nv_acoshf(float __a);
__DEVICE__ double __nv_asin(double __a);
__DEVICE__ float __nv_asinf(float __a);
__DEVICE__ double __nv_asinh(double __a);
__DEVICE__ float __nv_asinhf(float __a);
__DEVICE__ double __nv_atan2(double __a, double __b);
__DEVICE__ float __nv_atan2f(float __a, float __b);
__DEVICE__ double __nv_atan(double __a);
__DEVICE__ float __nv_atanf(float __a);
__DEVICE__ double __nv_atanh(double __a);
__DEVICE__ float __nv_atanhf(float __a);
__DEVICE__ int __nv_brev(int __a);
__DEVICE__ long long __nv_brevll(long long __a);
__DEVICE__ int __nv_byte_perm(int __a, int __b, int __c);
__DEVICE__ double __nv_cbrt(double __a);
__DEVICE__ float __nv_cbrtf(float __a);
__DEVICE__ double __nv_ceil(double __a);
__DEVICE__ float __nv_ceilf(float __a);
__DEVICE__ int __nv_clz(int __a);
__DEVICE__ int __nv_clzll(long long __a);
__DEVICE__ double __nv_copysign(double __a, double __b);
__DEVICE__ float __nv_copysignf(float __a, float __b);
__DEVICE__ double __nv_cos(double __a);
__DEVICE__ float __nv_cosf(float __a);
__DEVICE__ double __nv_cosh(double __a);
__DEVICE__ float __nv_coshf(float __a);
__DEVICE__ double __nv_cospi(double __a);
__DEVICE__ float __nv_cospif(float __a);
__DEVICE__ double __nv_cyl_bessel_i0(double __a);
__DEVICE__ float __nv_cyl_bessel_i0f(float __a);
__DEVICE__ double __nv_cyl_bessel_i1(double __a);
__DEVICE__ float __nv_cyl_bessel_i1f(float __a);
__DEVICE__ double __nv_dadd_rd(double __a, double __b);
__DEVICE__ double __nv_dadd_rn(double __a, double __b);
__DEVICE__ double __nv_dadd_ru(double __a, double __b);
__DEVICE__ double __nv_dadd_rz(double __a, double __b);
__DEVICE__ double __nv_ddiv_rd(double __a, double __b);
__DEVICE__ double __nv_ddiv_rn(double __a, double __b);
__DEVICE__ double __nv_ddiv_ru(double __a, double __b);
__DEVICE__ double __nv_ddiv_rz(double __a, double __b);
__DEVICE__ double __nv_dmul_rd(double __a, double __b);
__DEVICE__ double __nv_dmul_rn(double __a, double __b);
__DEVICE__ double __nv_dmul_ru(double __a, double __b);
__DEVICE__ double __nv_dmul_rz(double __a, double __b);
__DEVICE__ float __nv_double2float_rd(double __a);
__DEVICE__ float __nv_double2float_rn(double __a);
__DEVICE__ float __nv_double2float_ru(double __a);
__DEVICE__ float __nv_double2float_rz(double __a);
__DEVICE__ int __nv_double2hiint(double __a);
__DEVICE__ int __nv_double2int_rd(double __a);
__DEVICE__ int __nv_double2int_rn(double __a);
__DEVICE__ int __nv_double2int_ru(double __a);
__DEVICE__ int __nv_double2int_rz(double __a);
__DEVICE__ long long __nv_double2ll_rd(double __a);
__DEVICE__ long long __nv_double2ll_rn(double __a);
__DEVICE__ long long __nv_double2ll_ru(double __a);
__DEVICE__ long long __nv_double2ll_rz(double __a);
__DEVICE__ int __nv_double2loint(double __a);
__DEVICE__ unsigned int __nv_double2uint_rd(double __a);
__DEVICE__ unsigned int __nv_double2uint_rn(double __a);
__DEVICE__ unsigned int __nv_double2uint_ru(double __a);
__DEVICE__ unsigned int __nv_double2uint_rz(double __a);
__DEVICE__ unsigned long long __nv_double2ull_rd(double __a);
__DEVICE__ unsigned long long __nv_double2ull_rn(double __a);
__DEVICE__ unsigned long long __nv_double2ull_ru(double __a);
__DEVICE__ unsigned long long __nv_double2ull_rz(double __a);
__DEVICE__ unsigned long long __nv_double_as_longlong(double __a);
__DEVICE__ double __nv_drcp_rd(double __a);
__DEVICE__ double __nv_drcp_rn(double __a);
__DEVICE__ double __nv_drcp_ru(double __a);
__DEVICE__ double __nv_drcp_rz(double __a);
__DEVICE__ double __nv_dsqrt_rd(double __a);
__DEVICE__ double __nv_dsqrt_rn(double __a);
__DEVICE__ double __nv_dsqrt_ru(double __a);
__DEVICE__ double __nv_dsqrt_rz(double __a);
__DEVICE__ double __nv_dsub_rd(double __a, double __b);
__DEVICE__ double __nv_dsub_rn(double __a, double __b);
__DEVICE__ double __nv_dsub_ru(double __a, double __b);
__DEVICE__ double __nv_dsub_rz(double __a, double __b);
__DEVICE__ double __nv_erfc(double __a);
__DEVICE__ float __nv_erfcf(float __a);
__DEVICE__ double __nv_erfcinv(double __a);
__DEVICE__ float __nv_erfcinvf(float __a);
__DEVICE__ double __nv_erfcx(double __a);
__DEVICE__ float __nv_erfcxf(float __a);
__DEVICE__ double __nv_erf(double __a);
__DEVICE__ float __nv_erff(float __a);
__DEVICE__ double __nv_erfinv(double __a);
__DEVICE__ float __nv_erfinvf(float __a);
__DEVICE__ double __nv_exp10(double __a);
__DEVICE__ float __nv_exp10f(float __a);
__DEVICE__ double __nv_exp2(double __a);
__DEVICE__ float __nv_exp2f(float __a);
__DEVICE__ double __nv_exp(double __a);
__DEVICE__ float __nv_expf(float __a);
__DEVICE__ double __nv_expm1(double __a);
__DEVICE__ float __nv_expm1f(float __a);
__DEVICE__ double __nv_fabs(double __a);
__DEVICE__ float __nv_fabsf(float __a);
__DEVICE__ float __nv_fadd_rd(float __a, float __b);
__DEVICE__ float __nv_fadd_rn(float __a, float __b);
__DEVICE__ float __nv_fadd_ru(float __a, float __b);
__DEVICE__ float __nv_fadd_rz(float __a, float __b);
__DEVICE__ float __nv_fast_cosf(float __a);
__DEVICE__ float __nv_fast_exp10f(float __a);
__DEVICE__ float __nv_fast_expf(float __a);
__DEVICE__ float __nv_fast_fdividef(float __a, float __b);
__DEVICE__ float __nv_fast_log10f(float __a);
__DEVICE__ float __nv_fast_log2f(float __a);
__DEVICE__ float __nv_fast_logf(float __a);
__DEVICE__ float __nv_fast_powf(float __a, float __b);
__DEVICE__ void __nv_fast_sincosf(float __a, float* __s, float* __c);
__DEVICE__ float __nv_fast_sinf(float __a);
__DEVICE__ float __nv_fast_tanf(float __a);
__DEVICE__ double __nv_fdim(double __a, double __b);
__DEVICE__ float __nv_fdimf(float __a, float __b);
__DEVICE__ float __nv_fdiv_rd(float __a, float __b);
__DEVICE__ float __nv_fdiv_rn(float __a, float __b);
__DEVICE__ float __nv_fdiv_ru(float __a, float __b);
__DEVICE__ float __nv_fdiv_rz(float __a, float __b);
__DEVICE__ int __nv_ffs(int __a);
__DEVICE__ int __nv_ffsll(long long __a);
__DEVICE__ int __nv_finitef(float __a);
__DEVICE__ unsigned short __nv_float2half_rn(float __a);
__DEVICE__ int __nv_float2int_rd(float __a);
__DEVICE__ int __nv_float2int_rn(float __a);
__DEVICE__ int __nv_float2int_ru(float __a);
__DEVICE__ int __nv_float2int_rz(float __a);
__DEVICE__ long long __nv_float2ll_rd(float __a);
__DEVICE__ long long __nv_float2ll_rn(float __a);
__DEVICE__ long long __nv_float2ll_ru(float __a);
__DEVICE__ long long __nv_float2ll_rz(float __a);
__DEVICE__ unsigned int __nv_float2uint_rd(float __a);
__DEVICE__ unsigned int __nv_float2uint_rn(float __a);
__DEVICE__ unsigned int __nv_float2uint_ru(float __a);
__DEVICE__ unsigned int __nv_float2uint_rz(float __a);
__DEVICE__ unsigned long long __nv_float2ull_rd(float __a);
__DEVICE__ unsigned long long __nv_float2ull_rn(float __a);
__DEVICE__ unsigned long long __nv_float2ull_ru(float __a);
__DEVICE__ unsigned long long __nv_float2ull_rz(float __a);
__DEVICE__ int __nv_float_as_int(float __a);
__DEVICE__ unsigned int __nv_float_as_uint(float __a);
__DEVICE__ double __nv_floor(double __a);
__DEVICE__ float __nv_floorf(float __a);
__DEVICE__ double __nv_fma(double __a, double __b, double __c);
__DEVICE__ float __nv_fmaf(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_ieee_rd(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_ieee_rn(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_ieee_ru(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_ieee_rz(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_rd(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_rn(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_ru(float __a, float __b, float __c);
__DEVICE__ float __nv_fmaf_rz(float __a, float __b, float __c);
__DEVICE__ double __nv_fma_rd(double __a, double __b, double __c);
__DEVICE__ double __nv_fma_rn(double __a, double __b, double __c);
__DEVICE__ double __nv_fma_ru(double __a, double __b, double __c);
__DEVICE__ double __nv_fma_rz(double __a, double __b, double __c);
__DEVICE__ double __nv_fmax(double __a, double __b);
__DEVICE__ float __nv_fmaxf(float __a, float __b);
__DEVICE__ double __nv_fmin(double __a, double __b);
__DEVICE__ float __nv_fminf(float __a, float __b);
__DEVICE__ double __nv_fmod(double __a, double __b);
__DEVICE__ float __nv_fmodf(float __a, float __b);
__DEVICE__ float __nv_fmul_rd(float __a, float __b);
__DEVICE__ float __nv_fmul_rn(float __a, float __b);
__DEVICE__ float __nv_fmul_ru(float __a, float __b);
__DEVICE__ float __nv_fmul_rz(float __a, float __b);
__DEVICE__ float __nv_frcp_rd(float __a);
__DEVICE__ float __nv_frcp_rn(float __a);
__DEVICE__ float __nv_frcp_ru(float __a);
__DEVICE__ float __nv_frcp_rz(float __a);
__DEVICE__ double __nv_frexp(double __a, int* __b);
__DEVICE__ float __nv_frexpf(float __a, int* __b);
__DEVICE__ float __nv_frsqrt_rn(float __a);
__DEVICE__ float __nv_fsqrt_rd(float __a);
__DEVICE__ float __nv_fsqrt_rn(float __a);
__DEVICE__ float __nv_fsqrt_ru(float __a);
__DEVICE__ float __nv_fsqrt_rz(float __a);
__DEVICE__ float __nv_fsub_rd(float __a, float __b);
__DEVICE__ float __nv_fsub_rn(float __a, float __b);
__DEVICE__ float __nv_fsub_ru(float __a, float __b);
__DEVICE__ float __nv_fsub_rz(float __a, float __b);
__DEVICE__ int __nv_hadd(int __a, int __b);
__DEVICE__ float __nv_half2float(unsigned short __h);
__DEVICE__ double __nv_hiloint2double(int __a, int __b);
__DEVICE__ double __nv_hypot(double __a, double __b);
__DEVICE__ float __nv_hypotf(float __a, float __b);
__DEVICE__ int __nv_ilogb(double __a);
__DEVICE__ int __nv_ilogbf(float __a);
__DEVICE__ double __nv_int2double_rn(int __a);
__DEVICE__ float __nv_int2float_rd(int __a);
__DEVICE__ float __nv_int2float_rn(int __a);
__DEVICE__ float __nv_int2float_ru(int __a);
__DEVICE__ float __nv_int2float_rz(int __a);
__DEVICE__ float __nv_int_as_float(int __a);
__DEVICE__ int __nv_isfinited(double __a);
__DEVICE__ int __nv_isinfd(double __a);
__DEVICE__ int __nv_isinff(float __a);
__DEVICE__ int __nv_isnand(double __a);
__DEVICE__ int __nv_isnanf(float __a);
__DEVICE__ double __nv_j0(double __a);
__DEVICE__ float __nv_j0f(float __a);
__DEVICE__ double __nv_j1(double __a);
__DEVICE__ float __nv_j1f(float __a);
__DEVICE__ float __nv_jnf(int __a, float __b);
__DEVICE__ double __nv_jn(int __a, double __b);
__DEVICE__ double __nv_ldexp(double __a, int __b);
__DEVICE__ float __nv_ldexpf(float __a, int __b);
__DEVICE__ double __nv_lgamma(double __a);
__DEVICE__ float __nv_lgammaf(float __a);
__DEVICE__ double __nv_ll2double_rd(long long __a);
__DEVICE__ double __nv_ll2double_rn(long long __a);
__DEVICE__ double __nv_ll2double_ru(long long __a);
__DEVICE__ double __nv_ll2double_rz(long long __a);
__DEVICE__ float __nv_ll2float_rd(long long __a);
__DEVICE__ float __nv_ll2float_rn(long long __a);
__DEVICE__ float __nv_ll2float_ru(long long __a);
__DEVICE__ float __nv_ll2float_rz(long long __a);
__DEVICE__ long long __nv_llabs(long long __a);
__DEVICE__ long long __nv_llmax(long long __a, long long __b);
__DEVICE__ long long __nv_llmin(long long __a, long long __b);
__DEVICE__ long long __nv_llrint(double __a);
__DEVICE__ long long __nv_llrintf(float __a);
__DEVICE__ long long __nv_llround(double __a);
__DEVICE__ long long __nv_llroundf(float __a);
__DEVICE__ double __nv_log10(double __a);
__DEVICE__ float __nv_log10f(float __a);
__DEVICE__ double __nv_log1p(double __a);
__DEVICE__ float __nv_log1pf(float __a);
__DEVICE__ double __nv_log2(double __a);
__DEVICE__ float __nv_log2f(float __a);
__DEVICE__ double __nv_logb(double __a);
__DEVICE__ float __nv_logbf(float __a);
__DEVICE__ double __nv_log(double __a);
__DEVICE__ float __nv_logf(float __a);
__DEVICE__ double __nv_longlong_as_double(long long __a);
__DEVICE__ int __nv_max(int __a, int __b);
__DEVICE__ int __nv_min(int __a, int __b);
__DEVICE__ double __nv_modf(double __a, double* __b);
__DEVICE__ float __nv_modff(float __a, float* __b);
__DEVICE__ int __nv_mul24(int __a, int __b);
__DEVICE__ long long __nv_mul64hi(long long __a, long long __b);
__DEVICE__ int __nv_mulhi(int __a, int __b);
__DEVICE__ double __nv_nan(const signed char* __a);
__DEVICE__ float __nv_nanf(const signed char* __a);
__DEVICE__ double __nv_nearbyint(double __a);
__DEVICE__ float __nv_nearbyintf(float __a);
__DEVICE__ double __nv_nextafter(double __a, double __b);
__DEVICE__ float __nv_nextafterf(float __a, float __b);
__DEVICE__ double __nv_norm3d(double __a, double __b, double __c);
__DEVICE__ float __nv_norm3df(float __a, float __b, float __c);
__DEVICE__ double __nv_norm4d(double __a, double __b, double __c, double __d);
__DEVICE__ float __nv_norm4df(float __a, float __b, float __c, float __d);
__DEVICE__ double __nv_normcdf(double __a);
__DEVICE__ float __nv_normcdff(float __a);
__DEVICE__ double __nv_normcdfinv(double __a);
__DEVICE__ float __nv_normcdfinvf(float __a);
__DEVICE__ float __nv_normf(int __a, const float* __b);
__DEVICE__ double __nv_norm(int __a, const double* __b);
__DEVICE__ int __nv_popc(unsigned int __a);
__DEVICE__ int __nv_popcll(unsigned long long __a);
__DEVICE__ double __nv_pow(double __a, double __b);
__DEVICE__ float __nv_powf(float __a, float __b);
__DEVICE__ double __nv_powi(double __a, int __b);
__DEVICE__ float __nv_powif(float __a, int __b);
__DEVICE__ double __nv_rcbrt(double __a);
__DEVICE__ float __nv_rcbrtf(float __a);
__DEVICE__ double __nv_rcp64h(double __a);
__DEVICE__ double __nv_remainder(double __a, double __b);
__DEVICE__ float __nv_remainderf(float __a, float __b);
__DEVICE__ double __nv_remquo(double __a, double __b, int* __c);
__DEVICE__ float __nv_remquof(float __a, float __b, int* __c);
__DEVICE__ int __nv_rhadd(int __a, int __b);
__DEVICE__ double __nv_rhypot(double __a, double __b);
__DEVICE__ float __nv_rhypotf(float __a, float __b);
__DEVICE__ double __nv_rint(double __a);
__DEVICE__ float __nv_rintf(float __a);
__DEVICE__ double __nv_rnorm3d(double __a, double __b, double __c);
__DEVICE__ float __nv_rnorm3df(float __a, float __b, float __c);
__DEVICE__ double __nv_rnorm4d(double __a, double __b, double __c, double __d);
__DEVICE__ float __nv_rnorm4df(float __a, float __b, float __c, float __d);
__DEVICE__ float __nv_rnormf(int __a, const float* __b);
__DEVICE__ double __nv_rnorm(int __a, const double* __b);
__DEVICE__ double __nv_round(double __a);
__DEVICE__ float __nv_roundf(float __a);
__DEVICE__ double __nv_rsqrt(double __a);
__DEVICE__ float __nv_rsqrtf(float __a);
__DEVICE__ int __nv_sad(int __a, int __b, int __c);
__DEVICE__ float __nv_saturatef(float __a);
__DEVICE__ double __nv_scalbn(double __a, int __b);
__DEVICE__ float __nv_scalbnf(float __a, int __b);
__DEVICE__ int __nv_signbitd(double __a);
__DEVICE__ int __nv_signbitf(float __a);
__DEVICE__ void __nv_sincos(double __a, double* __b, double* __c);
__DEVICE__ void __nv_sincosf(float __a, float* __b, float* __c);
__DEVICE__ void __nv_sincospi(double __a, double* __b, double* __c);
__DEVICE__ void __nv_sincospif(float __a, float* __b, float* __c);
__DEVICE__ double __nv_sin(double __a);
__DEVICE__ float __nv_sinf(float __a);
__DEVICE__ double __nv_sinh(double __a);
__DEVICE__ float __nv_sinhf(float __a);
__DEVICE__ double __nv_sinpi(double __a);
__DEVICE__ float __nv_sinpif(float __a);
__DEVICE__ double __nv_sqrt(double __a);
__DEVICE__ float __nv_sqrtf(float __a);
__DEVICE__ double __nv_tan(double __a);
__DEVICE__ float __nv_tanf(float __a);
__DEVICE__ double __nv_tanh(double __a);
__DEVICE__ float __nv_tanhf(float __a);
__DEVICE__ double __nv_tgamma(double __a);
__DEVICE__ float __nv_tgammaf(float __a);
__DEVICE__ double __nv_trunc(double __a);
__DEVICE__ float __nv_truncf(float __a);
__DEVICE__ int __nv_uhadd(unsigned int __a, unsigned int __b);
__DEVICE__ double __nv_uint2double_rn(unsigned int __i);
__DEVICE__ float __nv_uint2float_rd(unsigned int __a);
__DEVICE__ float __nv_uint2float_rn(unsigned int __a);
__DEVICE__ float __nv_uint2float_ru(unsigned int __a);
__DEVICE__ float __nv_uint2float_rz(unsigned int __a);
__DEVICE__ float __nv_uint_as_float(unsigned int __a);
__DEVICE__ double __nv_ull2double_rd(unsigned long long __a);
__DEVICE__ double __nv_ull2double_rn(unsigned long long __a);
__DEVICE__ double __nv_ull2double_ru(unsigned long long __a);
__DEVICE__ double __nv_ull2double_rz(unsigned long long __a);
__DEVICE__ float __nv_ull2float_rd(unsigned long long __a);
__DEVICE__ float __nv_ull2float_rn(unsigned long long __a);
__DEVICE__ float __nv_ull2float_ru(unsigned long long __a);
__DEVICE__ float __nv_ull2float_rz(unsigned long long __a);
__DEVICE__ unsigned long long __nv_ullmax(unsigned long long __a, unsigned long long __b);
__DEVICE__ unsigned long long __nv_ullmin(unsigned long long __a, unsigned long long __b);
__DEVICE__ unsigned int __nv_umax(unsigned int __a, unsigned int __b);
__DEVICE__ unsigned int __nv_umin(unsigned int __a, unsigned int __b);
__DEVICE__ unsigned int __nv_umul24(unsigned int __a, unsigned int __b);
__DEVICE__ unsigned long long __nv_umul64hi(unsigned long long __a, unsigned long long __b);
__DEVICE__ unsigned int __nv_umulhi(unsigned int __a, unsigned int __b);
__DEVICE__ unsigned int __nv_urhadd(unsigned int __a, unsigned int __b);
__DEVICE__ unsigned int __nv_usad(unsigned int __a, unsigned int __b, unsigned int __c);
__DEVICE__ double __nv_y0(double __a);
__DEVICE__ float __nv_y0f(float __a);
__DEVICE__ double __nv_y1(double __a);
__DEVICE__ float __nv_y1f(float __a);
__DEVICE__ float __nv_ynf(int __a, float __b);
__DEVICE__ double __nv_yn(int __a, double __b);
#if defined(__cplusplus)
} // extern "C"
#endif
#endif // __CLANG_CUDA_LIBDEVICE_DECLARES_H__

View File

@@ -0,0 +1,809 @@
/*===---- __clang_cuda_math.h - Device-side CUDA math support --------------===
*
* Part of the LLVM Project, 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
*
*===-----------------------------------------------------------------------===
*/
#ifndef __CLANG_CUDA_MATH_H__
#define __CLANG_CUDA_MATH_H__
#ifndef __CUDA__
# error "This file is for CUDA compilation only."
#endif
// The __CLANG_GPU_DISABLE_MATH_WRAPPERS macro provides a way to let standard
// libcalls reach the link step instead of being eagerly replaced.
#ifndef __CLANG_GPU_DISABLE_MATH_WRAPPERS
// __DEVICE__ is a helper macro with common set of attributes for the wrappers
// we implement in this file. We need static in order to avoid emitting unused
// functions and __forceinline__ helps inlining these wrappers at -O1.
# pragma push_macro("__DEVICE__")
# define __DEVICE__ static __device__ __forceinline__
// Specialized version of __DEVICE__ for functions with void return type.
# pragma push_macro("__DEVICE_VOID__")
# define __DEVICE_VOID__ __DEVICE__
// libdevice provides fast low precision and slow full-recision implementations
// for some functions. Which one gets selected depends on
// __CLANG_CUDA_APPROX_TRANSCENDENTALS__ which gets defined by clang if
// -ffast-math or -fgpu-approx-transcendentals are in effect.
# pragma push_macro("__FAST_OR_SLOW")
# if defined(__CLANG_GPU_APPROX_TRANSCENDENTALS__)
# define __FAST_OR_SLOW(fast, slow) fast
# else
# define __FAST_OR_SLOW(fast, slow) slow
# endif
__DEVICE__ int abs(int __a)
{
return __nv_abs(__a);
}
__DEVICE__ double fabs(double __a)
{
return __nv_fabs(__a);
}
__DEVICE__ double acos(double __a)
{
return __nv_acos(__a);
}
__DEVICE__ float acosf(float __a)
{
return __nv_acosf(__a);
}
__DEVICE__ double acosh(double __a)
{
return __nv_acosh(__a);
}
__DEVICE__ float acoshf(float __a)
{
return __nv_acoshf(__a);
}
__DEVICE__ double asin(double __a)
{
return __nv_asin(__a);
}
__DEVICE__ float asinf(float __a)
{
return __nv_asinf(__a);
}
__DEVICE__ double asinh(double __a)
{
return __nv_asinh(__a);
}
__DEVICE__ float asinhf(float __a)
{
return __nv_asinhf(__a);
}
__DEVICE__ double atan(double __a)
{
return __nv_atan(__a);
}
__DEVICE__ double atan2(double __a, double __b)
{
return __nv_atan2(__a, __b);
}
__DEVICE__ float atan2f(float __a, float __b)
{
return __nv_atan2f(__a, __b);
}
__DEVICE__ float atanf(float __a)
{
return __nv_atanf(__a);
}
__DEVICE__ double atanh(double __a)
{
return __nv_atanh(__a);
}
__DEVICE__ float atanhf(float __a)
{
return __nv_atanhf(__a);
}
__DEVICE__ double cbrt(double __a)
{
return __nv_cbrt(__a);
}
__DEVICE__ float cbrtf(float __a)
{
return __nv_cbrtf(__a);
}
__DEVICE__ double ceil(double __a)
{
return __nv_ceil(__a);
}
__DEVICE__ float ceilf(float __a)
{
return __nv_ceilf(__a);
}
__DEVICE__ double copysign(double __a, double __b)
{
return __nv_copysign(__a, __b);
}
__DEVICE__ float copysignf(float __a, float __b)
{
return __nv_copysignf(__a, __b);
}
__DEVICE__ double cos(double __a)
{
return __nv_cos(__a);
}
__DEVICE__ float cosf(float __a)
{
return __FAST_OR_SLOW(__nv_fast_cosf, __nv_cosf)(__a);
}
__DEVICE__ double cosh(double __a)
{
return __nv_cosh(__a);
}
__DEVICE__ float coshf(float __a)
{
return __nv_coshf(__a);
}
__DEVICE__ double cospi(double __a)
{
return __nv_cospi(__a);
}
__DEVICE__ float cospif(float __a)
{
return __nv_cospif(__a);
}
__DEVICE__ double cyl_bessel_i0(double __a)
{
return __nv_cyl_bessel_i0(__a);
}
__DEVICE__ float cyl_bessel_i0f(float __a)
{
return __nv_cyl_bessel_i0f(__a);
}
__DEVICE__ double cyl_bessel_i1(double __a)
{
return __nv_cyl_bessel_i1(__a);
}
__DEVICE__ float cyl_bessel_i1f(float __a)
{
return __nv_cyl_bessel_i1f(__a);
}
__DEVICE__ double erf(double __a)
{
return __nv_erf(__a);
}
__DEVICE__ double erfc(double __a)
{
return __nv_erfc(__a);
}
__DEVICE__ float erfcf(float __a)
{
return __nv_erfcf(__a);
}
__DEVICE__ double erfcinv(double __a)
{
return __nv_erfcinv(__a);
}
__DEVICE__ float erfcinvf(float __a)
{
return __nv_erfcinvf(__a);
}
__DEVICE__ double erfcx(double __a)
{
return __nv_erfcx(__a);
}
__DEVICE__ float erfcxf(float __a)
{
return __nv_erfcxf(__a);
}
__DEVICE__ float erff(float __a)
{
return __nv_erff(__a);
}
__DEVICE__ double erfinv(double __a)
{
return __nv_erfinv(__a);
}
__DEVICE__ float erfinvf(float __a)
{
return __nv_erfinvf(__a);
}
__DEVICE__ double exp(double __a)
{
return __nv_exp(__a);
}
__DEVICE__ double exp10(double __a)
{
return __nv_exp10(__a);
}
__DEVICE__ float exp10f(float __a)
{
return __nv_exp10f(__a);
}
__DEVICE__ double exp2(double __a)
{
return __nv_exp2(__a);
}
__DEVICE__ float exp2f(float __a)
{
return __nv_exp2f(__a);
}
__DEVICE__ float expf(float __a)
{
return __nv_expf(__a);
}
__DEVICE__ double expm1(double __a)
{
return __nv_expm1(__a);
}
__DEVICE__ float expm1f(float __a)
{
return __nv_expm1f(__a);
}
__DEVICE__ float fabsf(float __a)
{
return __nv_fabsf(__a);
}
__DEVICE__ double fdim(double __a, double __b)
{
return __nv_fdim(__a, __b);
}
__DEVICE__ float fdimf(float __a, float __b)
{
return __nv_fdimf(__a, __b);
}
__DEVICE__ double fdivide(double __a, double __b)
{
return __a / __b;
}
__DEVICE__ float fdividef(float __a, float __b)
{
# if __FAST_MATH__ && !__CUDA_PREC_DIV
return __nv_fast_fdividef(__a, __b);
# else
return __a / __b;
# endif
}
__DEVICE__ double floor(double __f)
{
return __nv_floor(__f);
}
__DEVICE__ float floorf(float __f)
{
return __nv_floorf(__f);
}
__DEVICE__ double fma(double __a, double __b, double __c)
{
return __nv_fma(__a, __b, __c);
}
__DEVICE__ float fmaf(float __a, float __b, float __c)
{
return __nv_fmaf(__a, __b, __c);
}
__DEVICE__ double fmax(double __a, double __b)
{
return __nv_fmax(__a, __b);
}
__DEVICE__ float fmaxf(float __a, float __b)
{
return __nv_fmaxf(__a, __b);
}
__DEVICE__ double fmin(double __a, double __b)
{
return __nv_fmin(__a, __b);
}
__DEVICE__ float fminf(float __a, float __b)
{
return __nv_fminf(__a, __b);
}
__DEVICE__ double fmod(double __a, double __b)
{
return __nv_fmod(__a, __b);
}
__DEVICE__ float fmodf(float __a, float __b)
{
return __nv_fmodf(__a, __b);
}
__DEVICE__ double frexp(double __a, int* __b)
{
return __nv_frexp(__a, __b);
}
__DEVICE__ float frexpf(float __a, int* __b)
{
return __nv_frexpf(__a, __b);
}
__DEVICE__ double hypot(double __a, double __b)
{
return __nv_hypot(__a, __b);
}
__DEVICE__ float hypotf(float __a, float __b)
{
return __nv_hypotf(__a, __b);
}
__DEVICE__ int ilogb(double __a)
{
return __nv_ilogb(__a);
}
__DEVICE__ int ilogbf(float __a)
{
return __nv_ilogbf(__a);
}
__DEVICE__ double j0(double __a)
{
return __nv_j0(__a);
}
__DEVICE__ float j0f(float __a)
{
return __nv_j0f(__a);
}
__DEVICE__ double j1(double __a)
{
return __nv_j1(__a);
}
__DEVICE__ float j1f(float __a)
{
return __nv_j1f(__a);
}
__DEVICE__ double jn(int __n, double __a)
{
return __nv_jn(__n, __a);
}
__DEVICE__ float jnf(int __n, float __a)
{
return __nv_jnf(__n, __a);
}
# if defined(__LP64__) || defined(_WIN64)
__DEVICE__ long labs(long __a)
{
return __nv_llabs(__a);
};
# else
__DEVICE__ long labs(long __a)
{
return __nv_abs(__a);
};
# endif
__DEVICE__ double ldexp(double __a, int __b)
{
return __nv_ldexp(__a, __b);
}
__DEVICE__ float ldexpf(float __a, int __b)
{
return __nv_ldexpf(__a, __b);
}
__DEVICE__ double lgamma(double __a)
{
return __nv_lgamma(__a);
}
__DEVICE__ float lgammaf(float __a)
{
return __nv_lgammaf(__a);
}
__DEVICE__ long long llabs(long long __a)
{
return __nv_llabs(__a);
}
__DEVICE__ long long llmax(long long __a, long long __b)
{
return __nv_llmax(__a, __b);
}
__DEVICE__ long long llmin(long long __a, long long __b)
{
return __nv_llmin(__a, __b);
}
__DEVICE__ long long llrint(double __a)
{
return __nv_llrint(__a);
}
__DEVICE__ long long llrintf(float __a)
{
return __nv_llrintf(__a);
}
__DEVICE__ long long llround(double __a)
{
return __nv_llround(__a);
}
__DEVICE__ long long llroundf(float __a)
{
return __nv_llroundf(__a);
}
__DEVICE__ double round(double __a)
{
return __nv_round(__a);
}
__DEVICE__ float roundf(float __a)
{
return __nv_roundf(__a);
}
__DEVICE__ double log(double __a)
{
return __nv_log(__a);
}
__DEVICE__ double log10(double __a)
{
return __nv_log10(__a);
}
__DEVICE__ float log10f(float __a)
{
return __nv_log10f(__a);
}
__DEVICE__ double log1p(double __a)
{
return __nv_log1p(__a);
}
__DEVICE__ float log1pf(float __a)
{
return __nv_log1pf(__a);
}
__DEVICE__ double log2(double __a)
{
return __nv_log2(__a);
}
__DEVICE__ float log2f(float __a)
{
return __FAST_OR_SLOW(__nv_fast_log2f, __nv_log2f)(__a);
}
__DEVICE__ double logb(double __a)
{
return __nv_logb(__a);
}
__DEVICE__ float logbf(float __a)
{
return __nv_logbf(__a);
}
__DEVICE__ float logf(float __a)
{
return __FAST_OR_SLOW(__nv_fast_logf, __nv_logf)(__a);
}
# if defined(__LP64__) || defined(_WIN64)
__DEVICE__ long lrint(double __a)
{
return llrint(__a);
}
__DEVICE__ long lrintf(float __a)
{
return __float2ll_rn(__a);
}
__DEVICE__ long lround(double __a)
{
return llround(__a);
}
__DEVICE__ long lroundf(float __a)
{
return llroundf(__a);
}
# else
__DEVICE__ long lrint(double __a)
{
return (long) rint(__a);
}
__DEVICE__ long lrintf(float __a)
{
return __float2int_rn(__a);
}
__DEVICE__ long lround(double __a)
{
return round(__a);
}
__DEVICE__ long lroundf(float __a)
{
return roundf(__a);
}
# endif
__DEVICE__ int max(int __a, int __b)
{
return __nv_max(__a, __b);
}
__DEVICE__ int min(int __a, int __b)
{
return __nv_min(__a, __b);
}
__DEVICE__ double modf(double __a, double* __b)
{
return __nv_modf(__a, __b);
}
__DEVICE__ float modff(float __a, float* __b)
{
return __nv_modff(__a, __b);
}
__DEVICE__ double nearbyint(double __a)
{
return __builtin_nearbyint(__a);
}
__DEVICE__ float nearbyintf(float __a)
{
return __builtin_nearbyintf(__a);
}
__DEVICE__ double nextafter(double __a, double __b)
{
return __nv_nextafter(__a, __b);
}
__DEVICE__ float nextafterf(float __a, float __b)
{
return __nv_nextafterf(__a, __b);
}
__DEVICE__ double norm(int __dim, const double* __t)
{
return __nv_norm(__dim, __t);
}
__DEVICE__ double norm3d(double __a, double __b, double __c)
{
return __nv_norm3d(__a, __b, __c);
}
__DEVICE__ float norm3df(float __a, float __b, float __c)
{
return __nv_norm3df(__a, __b, __c);
}
__DEVICE__ double norm4d(double __a, double __b, double __c, double __d)
{
return __nv_norm4d(__a, __b, __c, __d);
}
__DEVICE__ float norm4df(float __a, float __b, float __c, float __d)
{
return __nv_norm4df(__a, __b, __c, __d);
}
__DEVICE__ double normcdf(double __a)
{
return __nv_normcdf(__a);
}
__DEVICE__ float normcdff(float __a)
{
return __nv_normcdff(__a);
}
__DEVICE__ double normcdfinv(double __a)
{
return __nv_normcdfinv(__a);
}
__DEVICE__ float normcdfinvf(float __a)
{
return __nv_normcdfinvf(__a);
}
__DEVICE__ float normf(int __dim, const float* __t)
{
return __nv_normf(__dim, __t);
}
__DEVICE__ double pow(double __a, double __b)
{
return __nv_pow(__a, __b);
}
__DEVICE__ float powf(float __a, float __b)
{
return __nv_powf(__a, __b);
}
__DEVICE__ double powi(double __a, int __b)
{
return __nv_powi(__a, __b);
}
__DEVICE__ float powif(float __a, int __b)
{
return __nv_powif(__a, __b);
}
__DEVICE__ double rcbrt(double __a)
{
return __nv_rcbrt(__a);
}
__DEVICE__ float rcbrtf(float __a)
{
return __nv_rcbrtf(__a);
}
__DEVICE__ double remainder(double __a, double __b)
{
return __nv_remainder(__a, __b);
}
__DEVICE__ float remainderf(float __a, float __b)
{
return __nv_remainderf(__a, __b);
}
__DEVICE__ double remquo(double __a, double __b, int* __c)
{
return __nv_remquo(__a, __b, __c);
}
__DEVICE__ float remquof(float __a, float __b, int* __c)
{
return __nv_remquof(__a, __b, __c);
}
__DEVICE__ double rhypot(double __a, double __b)
{
return __nv_rhypot(__a, __b);
}
__DEVICE__ float rhypotf(float __a, float __b)
{
return __nv_rhypotf(__a, __b);
}
// __nv_rint* in libdevice is buggy and produces incorrect results.
__DEVICE__ double rint(double __a)
{
return __builtin_rint(__a);
}
__DEVICE__ float rintf(float __a)
{
return __builtin_rintf(__a);
}
__DEVICE__ double rnorm(int __a, const double* __b)
{
return __nv_rnorm(__a, __b);
}
__DEVICE__ double rnorm3d(double __a, double __b, double __c)
{
return __nv_rnorm3d(__a, __b, __c);
}
__DEVICE__ float rnorm3df(float __a, float __b, float __c)
{
return __nv_rnorm3df(__a, __b, __c);
}
__DEVICE__ double rnorm4d(double __a, double __b, double __c, double __d)
{
return __nv_rnorm4d(__a, __b, __c, __d);
}
__DEVICE__ float rnorm4df(float __a, float __b, float __c, float __d)
{
return __nv_rnorm4df(__a, __b, __c, __d);
}
__DEVICE__ float rnormf(int __dim, const float* __t)
{
return __nv_rnormf(__dim, __t);
}
__DEVICE__ double rsqrt(double __a)
{
return __nv_rsqrt(__a);
}
__DEVICE__ float rsqrtf(float __a)
{
return __nv_rsqrtf(__a);
}
__DEVICE__ double scalbn(double __a, int __b)
{
return __nv_scalbn(__a, __b);
}
__DEVICE__ float scalbnf(float __a, int __b)
{
return __nv_scalbnf(__a, __b);
}
__DEVICE__ double scalbln(double __a, long __b)
{
if (__b > INT_MAX)
{
return __a > 0 ? HUGE_VAL : -HUGE_VAL;
}
if (__b < INT_MIN)
{
return __a > 0 ? 0.0 : -0.0;
}
return scalbn(__a, (int) __b);
}
__DEVICE__ float scalblnf(float __a, long __b)
{
if (__b > INT_MAX)
{
return __a > 0 ? HUGE_VALF : -HUGE_VALF;
}
if (__b < INT_MIN)
{
return __a > 0 ? 0.f : -0.f;
}
return scalbnf(__a, (int) __b);
}
__DEVICE__ double sin(double __a)
{
return __nv_sin(__a);
}
__DEVICE_VOID__ void sincos(double __a, double* __s, double* __c)
{
return __nv_sincos(__a, __s, __c);
}
__DEVICE_VOID__ void sincosf(float __a, float* __s, float* __c)
{
return __FAST_OR_SLOW(__nv_fast_sincosf, __nv_sincosf)(__a, __s, __c);
}
__DEVICE_VOID__ void sincospi(double __a, double* __s, double* __c)
{
return __nv_sincospi(__a, __s, __c);
}
__DEVICE_VOID__ void sincospif(float __a, float* __s, float* __c)
{
return __nv_sincospif(__a, __s, __c);
}
__DEVICE__ float sinf(float __a)
{
return __FAST_OR_SLOW(__nv_fast_sinf, __nv_sinf)(__a);
}
__DEVICE__ double sinh(double __a)
{
return __nv_sinh(__a);
}
__DEVICE__ float sinhf(float __a)
{
return __nv_sinhf(__a);
}
__DEVICE__ double sinpi(double __a)
{
return __nv_sinpi(__a);
}
__DEVICE__ float sinpif(float __a)
{
return __nv_sinpif(__a);
}
__DEVICE__ double sqrt(double __a)
{
return __nv_sqrt(__a);
}
__DEVICE__ float sqrtf(float __a)
{
return __nv_sqrtf(__a);
}
__DEVICE__ double tan(double __a)
{
return __nv_tan(__a);
}
__DEVICE__ float tanf(float __a)
{
return __nv_tanf(__a);
}
__DEVICE__ double tanh(double __a)
{
return __nv_tanh(__a);
}
__DEVICE__ float tanhf(float __a)
{
return __nv_tanhf(__a);
}
__DEVICE__ double tgamma(double __a)
{
return __nv_tgamma(__a);
}
__DEVICE__ float tgammaf(float __a)
{
return __nv_tgammaf(__a);
}
__DEVICE__ double trunc(double __a)
{
return __nv_trunc(__a);
}
__DEVICE__ float truncf(float __a)
{
return __nv_truncf(__a);
}
__DEVICE__ unsigned long long ullmax(unsigned long long __a, unsigned long long __b)
{
return __nv_ullmax(__a, __b);
}
__DEVICE__ unsigned long long ullmin(unsigned long long __a, unsigned long long __b)
{
return __nv_ullmin(__a, __b);
}
__DEVICE__ unsigned int umax(unsigned int __a, unsigned int __b)
{
return __nv_umax(__a, __b);
}
__DEVICE__ unsigned int umin(unsigned int __a, unsigned int __b)
{
return __nv_umin(__a, __b);
}
__DEVICE__ double y0(double __a)
{
return __nv_y0(__a);
}
__DEVICE__ float y0f(float __a)
{
return __nv_y0f(__a);
}
__DEVICE__ double y1(double __a)
{
return __nv_y1(__a);
}
__DEVICE__ float y1f(float __a)
{
return __nv_y1f(__a);
}
__DEVICE__ double yn(int __a, double __b)
{
return __nv_yn(__a, __b);
}
__DEVICE__ float ynf(int __a, float __b)
{
return __nv_ynf(__a, __b);
}
# pragma pop_macro("__DEVICE__")
# pragma pop_macro("__DEVICE_VOID__")
# pragma pop_macro("__FAST_OR_SLOW")
#endif // __CLANG_GPU_DISABLE_MATH_WRAPPERS
#endif // __CLANG_CUDA_MATH_H__

View File

@@ -0,0 +1,438 @@
/*===---- HostJIT CUDA runtime wrapper - replaces clang's wrapper ----------===
*
* This is a self-contained replacement for clang's __clang_cuda_runtime_wrapper.h.
* Instead of #include_next-ing the real wrapper (which has fragile ordering
* dependencies on system headers and CUDA toolkit version-specific branches),
* we directly include only the clang-provided CUDA helper headers we need and
* pull in the CUDA toolkit headers with explicit preprocessor guards.
*
* Key design decision: all clang-provided device function implementations and
* CCCL-required intrinsics are defined BEFORE any CUDA toolkit headers that
* might transitively include CCCL (via libcudacxx standard headers on our
* include path). This eliminates the need for forward declarations.
*
* Assumptions:
* - CUDA >= 9.0 (no legacy code paths)
* - Clang CUDA compilation (__CUDA__ && __clang__)
* - Freestanding: all standard headers are stubs or from libcudacxx
* - cuda::std is bridged into std via using-directive
*===-----------------------------------------------------------------------===*/
#ifndef __CLANG_CUDA_RUNTIME_WRAPPER_H__
#define __CLANG_CUDA_RUNTIME_WRAPPER_H__
#pragma clang system_header
#if defined(__CUDA__) && defined(__clang__)
// ============================================================================
// Phase 1: Forward-declare device math overloads before any <cmath> inclusion
// ============================================================================
// This prevents constexpr std library math functions from becoming implicitly
// host+device, which would block our __device__ overloads later.
# include <__clang_cuda_math_forward_declares.h>
// ============================================================================
// Phase 2: Device-side definitions before any CUDA toolkit headers
// ============================================================================
// Everything here uses only compiler builtins and our stubs. No CUDA toolkit
// headers are included yet, so nothing can transitively pull in CCCL.
# pragma push_macro("__THROW")
# pragma push_macro("__CUDA_ARCH__")
# ifndef __CUDA_ARCH__
# define __CUDA_ARCH__ 9999
# endif
// host_defines.h provides __device__, __host__, __forceinline__ macros.
// Its only transitive dep (ctype.h) hits our stub.
# define __CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__
# define __CUDACC__
# define __CUDA_LIBDEVICE__
# include "host_defines.h"
// ---- Builtin variables (threadIdx, blockIdx, etc.) ----
# include "__clang_cuda_builtin_vars.h"
// ---- Stubs needed by clang device function headers below ----
# include <climits>
# include <cmath>
# include <cstddef>
// string.h must precede __clang_cuda_device_functions.h: cuda_fp16.hpp uses
// memcpy from __host__ __device__ ctors. device_functions.h only declares a
// __device__ memcpy, so the host-side call site needs the stub's host-callable
// __builtin_memcpy overload visible first.
# include <string.h>
// ---- Clang device function wrappers (local copies, CUDA < 9.0 removed) ----
// NOTE: libdevice_declares.h must precede device_functions.h — the latter calls
// __nv_* symbols that are declared in the former.
// clang-format off
# include "__clang_cuda_libdevice_declares.h"
# include "__clang_cuda_device_functions.h"
// clang-format on
# include "__clang_cuda_math.h"
// ---- Address-space intrinsics needed by CCCL headers ----
// (e.g. cuda/__memory/address_space.h, cuda/__ptx/ptx_helper_functions.h)
static __device__ __forceinline__ __attribute__((const)) unsigned int __isGlobal(const void* p)
{
return __nvvm_isspacep_global(p);
}
static __device__ __forceinline__ __attribute__((const)) unsigned int __isShared(const void* p)
{
return __nvvm_isspacep_shared(p);
}
static __device__ __forceinline__ __attribute__((const)) unsigned int __isConstant(const void* p)
{
return __nvvm_isspacep_const(p);
}
static __device__ __forceinline__ __attribute__((const)) unsigned int __isLocal(const void* p)
{
return __nvvm_isspacep_local(p);
}
# define __FWD_DEVICE static __device__ __forceinline__
__FWD_DEVICE unsigned int __isClusterShared(const void*);
__FWD_DEVICE __SIZE_TYPE__ __cvta_generic_to_shared(const void*);
__FWD_DEVICE __SIZE_TYPE__ __cvta_generic_to_global(const void*);
__FWD_DEVICE void* __cvta_shared_to_generic(__SIZE_TYPE__);
__FWD_DEVICE void* __cvta_global_to_generic(__SIZE_TYPE__);
# undef __FWD_DEVICE
# ifndef _MSC_VER
__device__ bool __nv_fp128_isnan(__float128);
__device__ __float128 __nv_fp128_fmax(__float128, __float128);
__device__ __float128 __nv_fp128_fmin(__float128, __float128);
# endif
// ---- Bridge cuda::std into std ----
namespace cuda
{
namespace std
{
}
} // namespace cuda
namespace std
{
using namespace cuda::std;
}
// ============================================================================
// Phase 3: CUDA toolkit headers
// ============================================================================
// By this point all device-side functions and intrinsics are defined, so
// any transitive CCCL includes from these headers will find them.
# pragma push_macro("__CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__")
# define __DEVICE_LAUNCH_PARAMETERS_H__
// Guard out CUDA's declaration-only headers; clang provides its own.
# define __DEVICE_FUNCTIONS_H__
# define __MATH_FUNCTIONS_H__
# define __MATH_FUNCTIONS_HPP__
# define __COMMON_FUNCTIONS_H__
# define __DEVICE_FUNCTIONS_DECLS_H__
// ---- CUDA runtime types (cudaError_t, dim3, cudaStream_t, etc.) ----
// (host_defines.h already included in Phase 2)
# undef __CUDACC__
# include "cuda.h"
# include "driver_types.h"
# include "host_config.h"
# if !defined(CUDA_VERSION) || CUDA_VERSION < 9000
# error "Unsupported CUDA version (need >= 9.0)!"
# endif
// Clang does not have __nvvm_memcpy/__nvvm_memset; emulate with builtins.
# define __nvvm_memcpy(s, d, n, a) __builtin_memcpy(s, d, n)
# define __nvvm_memset(d, c, n, a) __builtin_memset(d, c, n)
// __THROW may be in a weird state; keep it empty for CUDA includes.
# undef __THROW
# define __THROW
// ============================================================================
// Phase 4: Device-side function definitions from CUDA toolkit .hpp files
// ============================================================================
// Poison __host__ to ensure none of these definitions get host attributes.
# pragma push_macro("__host__")
# define __host__ UNEXPECTED_HOST_ATTRIBUTE
// Redefine __forceinline__ to include __device__.
# pragma push_macro("__forceinline__")
# define __forceinline__ __device__ __inline__ __attribute__((always_inline))
// Math functions: use fast or accurate variants based on compiler flag.
# pragma push_macro("__USE_FAST_MATH__")
# if defined(__CLANG_GPU_APPROX_TRANSCENDENTALS__)
# define __USE_FAST_MATH__ 1
# endif
# include "crt/math_functions.hpp"
# pragma pop_macro("__USE_FAST_MATH__")
# pragma pop_macro("__forceinline__")
# undef __MATH_FUNCTIONS_HPP__
# undef __CUDABE__
// Re-include device functions with __host__ defined as empty to get
// the "other branch" of #if/#else in the .hpp files.
# define __host__
# undef __CUDABE__
# define __CUDACC__
// Atomic function declarations (became builtins in CUDA 9).
# include "device_atomic_functions.h"
# undef __DEVICE_FUNCTIONS_HPP__
# include "crt/device_double_functions.hpp"
# include "crt/device_functions.hpp"
# include "device_atomic_functions.hpp"
# include "sm_20_atomic_functions.hpp"
// sm_20_intrinsics.hpp defines __isGlobal etc. without const attribute.
// Rename them so the definitions from Phase 4 (with const) prevail.
# pragma push_macro("__isGlobal")
# pragma push_macro("__isShared")
# pragma push_macro("__isConstant")
# pragma push_macro("__isLocal")
# define __isGlobal __ignored_cuda___isGlobal
# define __isShared __ignored_cuda___isShared
# define __isConstant __ignored_cuda___isConstant
# define __isLocal __ignored_cuda___isLocal
# include "sm_20_intrinsics.hpp"
# pragma pop_macro("__isGlobal")
# pragma pop_macro("__isShared")
# pragma pop_macro("__isConstant")
# pragma pop_macro("__isLocal")
# include "sm_32_atomic_functions.hpp"
# pragma push_macro("__CUDA_ARCH__")
# undef __CUDA_ARCH__
# include "sm_60_atomic_functions.hpp"
# include "sm_61_intrinsics.hpp"
# pragma pop_macro("__CUDA_ARCH__")
# undef __MATH_FUNCTIONS_HPP__
// math_functions.hpp ::signbit conflicts with libstdc++ constexpr ::signbit.
# pragma push_macro("signbit")
# pragma push_macro("__GNUC__")
# undef __GNUC__
# define signbit __ignored_cuda_signbit
# pragma push_macro("_GLIBCXX_MATH_H")
# pragma push_macro("_LIBCPP_VERSION")
# undef _GLIBCXX_MATH_H
# ifdef _LIBCPP_VERSION
# define _LIBCPP_VERSION 3700
# endif
# include "crt/math_functions.hpp"
# pragma pop_macro("_GLIBCXX_MATH_H")
# pragma pop_macro("_LIBCPP_VERSION")
# pragma pop_macro("__GNUC__")
# pragma pop_macro("signbit")
# pragma pop_macro("__host__")
// ============================================================================
// Phase 5: cuda_runtime.h (first header that transitively pulls in CCCL)
// ============================================================================
// ============================================================================
// Phase 5: cuda_runtime.h (first header that transitively pulls in CCCL)
// ============================================================================
// Verify no libcudacxx header was pulled in yet. If this fires, a header
// above transitively included a system header that resolved to libcudacxx
// before all device-side definitions were ready.
# ifdef CCCL_VERSION
# error "libcudacxx was included before device-side definitions were set up"
# endif
# pragma push_macro("nv_weak")
# define nv_weak weak
# undef __CUDA_LIBDEVICE__
# define __CUDACC__
# include "cuda_runtime.h"
# pragma pop_macro("nv_weak")
# undef __CUDACC__
# define __CUDABE__
# include "crt/host_runtime.h"
// device_runtime.h defines __cxa_* macros that conflict with cxxabi.h.
# undef __cxa_vec_ctor
# undef __cxa_vec_cctor
# undef __cxa_vec_dtor
# undef __cxa_vec_new
# undef __cxa_vec_new2
# undef __cxa_vec_new3
# undef __cxa_vec_delete2
# undef __cxa_vec_delete
# undef __cxa_vec_delete3
# undef __cxa_pure_virtual
// Texture intrinsics (requires C++11).
# if __cplusplus >= 201103L
# include <__clang_cuda_texture_intrinsics.h>
# else
template <typename T>
struct __nv_tex_needs_cxx11
{
const static bool value = false;
};
template <class T>
__host__ __device__ void __nv_tex_surf_handler(const char* name, T* ptr, cudaTextureObject_t obj, float x)
{
_Static_assert(__nv_tex_needs_cxx11<T>::value, "Texture support requires C++11");
}
# endif
# include "surface_indirect_functions.h"
# if CUDA_VERSION < 13000
# include "texture_fetch_functions.h"
# endif
# include "texture_indirect_functions.h"
// ============================================================================
// Phase 7: Restore saved state
// ============================================================================
# pragma pop_macro("__CUDA_ARCH__")
# pragma pop_macro("__THROW")
# undef __CUDABE__
# define __CUDACC__
// ============================================================================
// Phase 8: Device-side system calls & std wrappers
// ============================================================================
extern "C" {
__device__ int vprintf(const char*, const char*);
__device__ void free(void*) __attribute((nothrow));
__device__ void* malloc(size_t) __attribute((nothrow)) __attribute__((malloc));
__device__ void
__assertfail(const char* __message, const char* __file, unsigned __line, const char* __function, size_t __charSize);
__device__ static inline void
__assert_fail(const char* __message, const char* __file, unsigned __line, const char* __function)
{
__assertfail(__message, __file, __line, __function, sizeof(char));
}
__device__ int printf(const char*, ...);
} // extern "C"
namespace std
{
__device__ static inline void free(void* __ptr)
{
::free(__ptr);
}
__device__ static inline void* malloc(size_t __size)
{
return ::malloc(__size);
}
} // namespace std
// ============================================================================
// Phase 9: Builtin variable conversion operators
// ============================================================================
// These need dim3 and uint3 to be fully defined (from vector_types.h, pulled
// in by driver_types.h in Phase 5).
__device__ inline __cuda_builtin_threadIdx_t::operator dim3() const
{
return dim3(x, y, z);
}
__device__ inline __cuda_builtin_threadIdx_t::operator uint3() const
{
return {x, y, z};
}
__device__ inline __cuda_builtin_blockIdx_t::operator dim3() const
{
return dim3(x, y, z);
}
__device__ inline __cuda_builtin_blockIdx_t::operator uint3() const
{
return {x, y, z};
}
__device__ inline __cuda_builtin_blockDim_t::operator dim3() const
{
return dim3(x, y, z);
}
__device__ inline __cuda_builtin_blockDim_t::operator uint3() const
{
return {x, y, z};
}
__device__ inline __cuda_builtin_gridDim_t::operator dim3() const
{
return dim3(x, y, z);
}
__device__ inline __cuda_builtin_gridDim_t::operator uint3() const
{
return {x, y, z};
}
// ============================================================================
// Phase 10: Remaining clang CUDA headers
// ============================================================================
# include <__clang_cuda_cmath.h>
# include <__clang_cuda_intrinsics.h>
// __clang_cuda_intrinsics.h provides `long` overloads for __ldcs/__ldcg/__ldcv
// but omits `unsigned long` (= uint64_t on 64-bit Linux). Add them here so
// iterators using uint64_t pointers (e.g. CacheModifiedInputIterator) compile.
# if defined(__LP64__)
inline __device__ unsigned long __ldcs(const unsigned long* __ptr)
{
unsigned long __ret;
asm("ld.global.cs.u64 %0, [%1];" : "=l"(__ret) : "l"(__ptr));
return __ret;
}
inline __device__ unsigned long __ldcg(const unsigned long* __ptr)
{
unsigned long __ret;
asm("ld.global.cg.u64 %0, [%1];" : "=l"(__ret) : "l"(__ptr));
return __ret;
}
inline __device__ unsigned long __ldcv(const unsigned long* __ptr)
{
unsigned long __ret;
asm("ld.global.cv.u64 %0, [%1];" : "=l"(__ret) : "l"(__ptr));
return __ret;
}
# endif // __LP64__
# include <__clang_cuda_complex_builtins.h>
// curand_mtgp32_kernel redefines blockDim/threadIdx with dim3/uint3 types,
// which is incompatible with our builtins. Force-include it with types
// redefined to our builtin types.
// Skip when cuRAND headers are unavailable (e.g. pip-installed toolkit).
# if __has_include("curand_mtgp32_kernel.h")
# pragma push_macro("dim3")
# pragma push_macro("uint3")
# define dim3 __cuda_builtin_blockDim_t
# define uint3 __cuda_builtin_threadIdx_t
# include "curand_mtgp32_kernel.h"
# pragma pop_macro("dim3")
# pragma pop_macro("uint3")
# endif
# pragma pop_macro("__CUDA_INCLUDE_COMPILER_INTERNAL_HEADERS__")
// Kernel launch configuration function.
# if CUDA_VERSION >= 9020
extern "C" unsigned __cudaPushCallConfiguration(dim3 gridDim, dim3 blockDim, size_t sharedMem = 0, void* stream = 0);
# endif
// The JIT shared library is linked without the C runtime (no libc on the link
// line) so atexit is unavailable. The CUDA module constructor calls atexit()
// to register a cleanup function. Provide a no-op stub — the JIT library is
// short-lived and unloaded explicitly.
# if !defined(__HOSTJIT_DEVICE_COMPILATION__)
# if defined(_MSC_VER)
extern "C" int atexit(void(__cdecl*)(void))
{
return 0;
}
# else
extern "C" int atexit(void (*)(void))
{
return 0;
}
# endif
# endif
#endif // __CUDA__ && __clang__
#endif // __CLANG_CUDA_RUNTIME_WRAPPER_H__

View File

@@ -0,0 +1,31 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
// Minimal freestanding-mode stub for <assert.h>.
//
// CUDA toolkit headers pulled in via libcudacxx's __floating_point/cuda_fp_types.h
// (e.g. cuda_fp8.hpp) include <assert.h> unconditionally. In the JIT compile
// environment we have no libc; treat assert(expr) as a no-op. This matches the
// effect of `-DNDEBUG`, which CCCL/CUB device code already expects.
#ifndef _HOSTJIT_ASSERT_H
#define _HOSTJIT_ASSERT_H
#ifdef __cplusplus
extern "C" {
#endif
#undef assert
#define assert(expr) ((void) 0)
#ifdef __cplusplus
}
#endif
#endif // _HOSTJIT_ASSERT_H

View File

@@ -0,0 +1,6 @@
// Minimal freestanding-mode stub for <cassert>.
// Just delegate to <assert.h>'s no-op assert.
#ifndef _HOSTJIT_CASSERT
#define _HOSTJIT_CASSERT
#include <assert.h>
#endif // _HOSTJIT_CASSERT

View File

@@ -0,0 +1,7 @@
// Minimal climits stub for CUDA JIT compilation
#ifndef _HOSTJIT_CLIMITS
#define _HOSTJIT_CLIMITS
#include <limits.h>
#endif // _HOSTJIT_CLIMITS

View File

@@ -0,0 +1,7 @@
// Minimal cmath stub for CUDA JIT compilation
#ifndef _HOSTJIT_CMATH
#define _HOSTJIT_CMATH
#include <math.h>
#endif // _HOSTJIT_CMATH

View File

@@ -0,0 +1,15 @@
// Minimal cstddef stub for CUDA JIT compilation
// Compatible with libcu++ which expects to pull types from global namespace
#ifndef _HOSTJIT_CSTDDEF
#define _HOSTJIT_CSTDDEF
#include <stddef.h>
namespace std {
using ::size_t;
using ::ptrdiff_t;
using nullptr_t = decltype(nullptr);
}
#endif // _HOSTJIT_CSTDDEF

View File

@@ -0,0 +1,12 @@
#ifndef _HOSTJIT_CSTDLIB
#define _HOSTJIT_CSTDLIB
#include <cstddef>
#define EXIT_SUCCESS 0
#define EXIT_FAILURE 1
#define RAND_MAX 2147483647
extern "C" {
void* malloc(size_t); void* calloc(size_t, size_t);
void* realloc(void*, size_t); void free(void*);
void abort(void); void exit(int); void _Exit(int);
}
#endif

View File

@@ -0,0 +1,3 @@
#ifndef _HOSTJIT_CTYPE_H
#define _HOSTJIT_CTYPE_H
#endif

View File

@@ -0,0 +1,75 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
// ClangJIT minimal stub for cuda/std/__cstdlib/aligned_alloc.h
//
// Problem: hostjit compiles with _CCCL_ENABLE_FREESTANDING=1 in both device
// and host passes. The host pass needs ::cuda::std::__aligned_alloc_host, but
// the real header gates that function on _CCCL_HOSTED(), which is 0 in a
// freestanding build.
//
// Solution: replace the entire header with a bare-metal stub that uses only
// compiler builtins (__builtin_malloc, __SIZE_TYPE__) and NO CCCL headers.
// Including CCCL headers from within this stub caused __clang_cuda_device_functions.h
// to be re-processed before __clang_cuda_libdevice_declares.h during device
// compilation, producing "undeclared identifier __nv_ull2float_rz" errors.
//
// __builtin_malloc is a compiler intrinsic — no headers required.
// __SIZE_TYPE__ is a compiler predefined macro equal to the platform size_t type.
//
// Neither path is ever actually called at runtime:
// - Host pass: CUB dispatch never calls aligned_alloc in our generated source.
// - Device pass: NV_IF_ELSE_TARGET discards the NV_IS_HOST branch at compile time.
#ifndef _CUDA_STD___CSTDLIB_ALIGNED_ALLOC_H
#define _CUDA_STD___CSTDLIB_ALIGNED_ALLOC_H
#if defined(__CUDA_ARCH__)
// ── Device compilation ────────────────────────────────────────────────────
// Provide cuda::std::aligned_alloc via the CUDA device syscall.
// The NV_IS_HOST branch of the CUB include chain is discarded by Clang's
// "if target" extension, so this function is never actually called.
extern "C" __device__ void* __cuda_syscall_aligned_malloc(__SIZE_TYPE__, __SIZE_TYPE__);
namespace cuda
{
namespace std
{
inline __device__ void* aligned_alloc(__SIZE_TYPE__ __align, __SIZE_TYPE__ __nbytes) noexcept
{
return ::__cuda_syscall_aligned_malloc(__nbytes, __align);
}
} // namespace std
} // namespace cuda
#else
// ── Host compilation ──────────────────────────────────────────────────────
// Define __aligned_alloc_host unconditionally so the CUB include chain
// compiles even when _CCCL_HOSTED() == 0. __builtin_malloc needs no headers.
namespace cuda
{
namespace std
{
inline void* __aligned_alloc_host(__SIZE_TYPE__, __SIZE_TYPE__ __nbytes) noexcept
{
return __builtin_malloc(__nbytes);
}
inline void* aligned_alloc(__SIZE_TYPE__ __align, __SIZE_TYPE__ __nbytes) noexcept
{
return ::cuda::std::__aligned_alloc_host(__align, __nbytes);
}
} // namespace std
} // namespace cuda
#endif // __CUDA_ARCH__
#endif // _CUDA_STD___CSTDLIB_ALIGNED_ALLOC_H

View File

@@ -0,0 +1,47 @@
// Minimal initializer_list stub for CUDA JIT compilation
#ifndef _HOSTJIT_INITIALIZER_LIST
#define _HOSTJIT_INITIALIZER_LIST
#include <cstddef>
namespace std {
template<class T>
class initializer_list {
public:
using value_type = T;
using reference = const T&;
using const_reference = const T&;
using size_type = size_t;
using iterator = const T*;
using const_iterator = const T*;
private:
const T* _begin;
size_t _size;
// This constructor is called by the compiler
constexpr initializer_list(const T* b, size_t s) noexcept
: _begin(b), _size(s) {}
public:
constexpr initializer_list() noexcept : _begin(nullptr), _size(0) {}
constexpr size_t size() const noexcept { return _size; }
constexpr const T* begin() const noexcept { return _begin; }
constexpr const T* end() const noexcept { return _begin + _size; }
};
template<class T>
constexpr const T* begin(initializer_list<T> il) noexcept {
return il.begin();
}
template<class T>
constexpr const T* end(initializer_list<T> il) noexcept {
return il.end();
}
} // namespace std
#endif // _HOSTJIT_INITIALIZER_LIST

View File

@@ -0,0 +1,61 @@
// Minimal <limits> stub for hostjit device compilation.
//
// Clang's __clang_cuda_cmath.h includes <limits> unconditionally, then expands
// __CUDA_CLANG_FN_INTEGER_OVERLOAD_1/2 macros that reference
// std::numeric_limits<__T>::is_integer in return-type SFINAE at parse time.
// Clang evaluates these dependent names during template parsing, so the struct
// must be declared — not just forward-declared — before the macro expansion.
//
// In the hostjit device-compilation include path, <limits> would normally
// resolve to libcudacxx/include/cuda/std/limits, which cascades through
// numeric_limits, bit_cast, popcount, etc. — incompatible with freestanding.
//
// This stub (found first on -internal-isystem) stops that cascade, providing
// only the two members that __clang_cuda_cmath.h actually inspects.
#pragma once
namespace std {
template <typename _Tp>
struct numeric_limits {
static constexpr bool is_specialized = false;
static constexpr bool is_integer = false;
};
// Integer specializations — needed so the SFINAE in __clang_cuda_cmath.h
// correctly dispatches integer arguments.
#define _HOSTJIT_NUM_LIM_INT(_T) \
template <> struct numeric_limits<_T> { \
static constexpr bool is_specialized = true; \
static constexpr bool is_integer = true; \
};
_HOSTJIT_NUM_LIM_INT(bool)
_HOSTJIT_NUM_LIM_INT(char)
_HOSTJIT_NUM_LIM_INT(signed char)
_HOSTJIT_NUM_LIM_INT(unsigned char)
_HOSTJIT_NUM_LIM_INT(short)
_HOSTJIT_NUM_LIM_INT(unsigned short)
_HOSTJIT_NUM_LIM_INT(int)
_HOSTJIT_NUM_LIM_INT(unsigned int)
_HOSTJIT_NUM_LIM_INT(long)
_HOSTJIT_NUM_LIM_INT(unsigned long)
_HOSTJIT_NUM_LIM_INT(long long)
_HOSTJIT_NUM_LIM_INT(unsigned long long)
#undef _HOSTJIT_NUM_LIM_INT
// Floating-point specializations.
#define _HOSTJIT_NUM_LIM_FP(_T) \
template <> struct numeric_limits<_T> { \
static constexpr bool is_specialized = true; \
static constexpr bool is_integer = false; \
};
_HOSTJIT_NUM_LIM_FP(float)
_HOSTJIT_NUM_LIM_FP(double)
_HOSTJIT_NUM_LIM_FP(long double)
#undef _HOSTJIT_NUM_LIM_FP
} // namespace std

View File

@@ -0,0 +1,21 @@
#ifndef _HOSTJIT_MATH_H
#define _HOSTJIT_MATH_H
// Macros needed by __clang_cuda_math.h
#define HUGE_VAL __builtin_huge_val()
#define HUGE_VALF __builtin_huge_valf()
#define HUGE_VALL __builtin_huge_vall()
#define INFINITY __builtin_inff()
#define NAN __builtin_nanf("")
#define MATH_ERRNO 1
#define MATH_ERREXCEPT 2
#define math_errhandling (MATH_ERRNO | MATH_ERREXCEPT)
#define FP_NAN 0
#define FP_INFINITE 1
#define FP_ZERO 2
#define FP_SUBNORMAL 3
#define FP_NORMAL 4
#define __signbit(x) __builtin_signbit(x)
#define __signbitl(x) __builtin_signbitl(x)
#endif

View File

@@ -0,0 +1,4 @@
#ifndef _HOSTJIT_MEMORY_H
#define _HOSTJIT_MEMORY_H
#include <string.h>
#endif

View File

@@ -0,0 +1,29 @@
#ifndef _HOSTJIT_NEW
#define _HOSTJIT_NEW
#include <cstddef>
namespace std {
struct nothrow_t { explicit nothrow_t() = default; };
extern const nothrow_t nothrow;
enum class align_val_t : size_t {};
}
// Placement new — needs __host__ __device__ for CUDA
#if defined(__CUDA__)
__host__ __device__
#endif
inline void* operator new(std::size_t, void* p) noexcept { return p; }
#if defined(__CUDA__)
__host__ __device__
#endif
inline void* operator new[](std::size_t, void* p) noexcept { return p; }
#if defined(__CUDA__)
__host__ __device__
#endif
inline void operator delete(void*, void*) noexcept {}
#if defined(__CUDA__)
__host__ __device__
#endif
inline void operator delete[](void*, void*) noexcept {}
#endif

View File

@@ -0,0 +1,11 @@
// Minimal stdlib.h stub for CUDA JIT compilation
#ifndef _HOSTJIT_STDLIB_H
#define _HOSTJIT_STDLIB_H
#include <cstdlib>
#ifdef _WIN32
extern "C" int _fltused = 0;
#endif // _WIN32
#endif // _HOSTJIT_STDLIB_H

View File

@@ -0,0 +1,72 @@
#ifndef _HOSTJIT_STRING_H
#define _HOSTJIT_STRING_H
#include <stddef.h>
#ifdef __cplusplus
extern "C" {
inline void* memcpy(void* __s1, const void* __s2, size_t __n)
{
return __builtin_memcpy(__s1, __s2, __n);
}
inline void* memset(void* __s, int __c, size_t __n)
{
return __builtin_memset(__s, __c, __n);
}
inline void* memmove(void* __s1, const void* __s2, size_t __n)
{
return __builtin_memmove(__s1, __s2, __n);
}
inline int memcmp(const void* __s1, const void* __s2, size_t __n)
{
return __builtin_memcmp(__s1, __s2, __n);
}
inline char* strchr(char* __s, int __c)
{
return __builtin_strchr(__s, __c);
}
inline char* strpbrk(char* __s1, const char* __s2)
{
return __builtin_strpbrk(__s1, __s2);
}
inline char* strrchr(char* __s, int __c)
{
return __builtin_strrchr(__s, __c);
}
inline void* memchr(void* __s, int __c, size_t __n)
{
return __builtin_memchr(__s, __c, __n);
}
inline char* strstr(char* __s1, const char* __s2)
{
return __builtin_strstr(__s1, __s2);
}
inline char* strcpy(char* __s1, const char* __s2)
{
return __builtin_strcpy(__s1, __s2);
}
inline char* strncpy(char* __s1, const char* __s2, size_t __n)
{
return __builtin_strncpy(__s1, __s2, __n);
}
inline int strcmp(const char* __s1, const char* __s2)
{
return __builtin_strcmp(__s1, __s2);
}
inline int strncmp(const char* __s1, const char* __s2, size_t __n)
{
return __builtin_strncmp(__s1, __s2, __n);
}
inline size_t strlen(const char* __s)
{
return __builtin_strlen(__s);
}
}
#else // ^^^ __cplusplus ^^^ / vvv !__cplusplus vvv
void* memcpy(void*, const void*, size_t);
void* memset(void*, int, size_t);
int memcmp(const void*, const void*, size_t);
void* memmove(void*, const void*, size_t);
size_t strlen(const char*);
#endif // !__cplusplus
#endif //_HOSTJIT_STRING_H

View File

@@ -0,0 +1,34 @@
// Minimal <utility> stub for hostjit device compilation.
//
// cuda_runtime.h includes <utility> for std::forward/std::move. In the
// hostjit device-compilation include path, <utility> resolves to
// libcudacxx/include/cuda/std/utility, which cascades into the full CCCL
// utility/iterator/concepts hierarchy — incompatible with freestanding mode.
//
// This stub (found first on -internal-isystem) stops that cascade.
// Only std::forward and std::move are provided because that is all
// cuda_runtime.h actually uses at the top level; the full CCCL hierarchy
// is not required for a simple host+device kernel.
#pragma once
namespace std {
template <typename _Tp> struct remove_reference { using type = _Tp; };
template <typename _Tp> struct remove_reference<_Tp&> { using type = _Tp; };
template <typename _Tp> struct remove_reference<_Tp&&>{ using type = _Tp; };
template <typename _Tp>
using remove_reference_t = typename remove_reference<_Tp>::type;
template <typename _Tp>
__host__ __device__ constexpr _Tp&&
forward(remove_reference_t<_Tp>& __t) noexcept { return static_cast<_Tp&&>(__t); }
template <typename _Tp>
__host__ __device__ constexpr _Tp&&
forward(remove_reference_t<_Tp>&& __t) noexcept { return static_cast<_Tp&&>(__t); }
template <typename _Tp>
__host__ __device__ constexpr remove_reference_t<_Tp>&&
move(_Tp&& __t) noexcept { return static_cast<remove_reference_t<_Tp>&&>(__t); }
} // namespace std

View File

@@ -0,0 +1,95 @@
#pragma once
#include <memory>
#include <string>
#include <hostjit/compiler.hpp>
#include <hostjit/config.hpp>
#include <hostjit/loader.hpp>
namespace hostjit
{
class JITCompiler
{
public:
// Create JIT compiler with default configuration (auto-detected)
JITCompiler();
// Create JIT compiler with custom configuration
explicit JITCompiler(const CompilerConfig& config);
~JITCompiler();
// Disable copy
JITCompiler(const JITCompiler&) = delete;
JITCompiler& operator=(const JITCompiler&) = delete;
// Compile CUDA source code to shared library and load it
// Returns true on success, false on failure
bool compile(const std::string& source_code);
// Get function pointer by name
// Returns nullptr if function not found
template <typename FuncType>
FuncType getFunction(const std::string& name)
{
if (!library_.isLoaded())
{
last_error_ = "No library loaded";
return nullptr;
}
auto func = library_.getFunction<FuncType>(name);
if (!func)
{
last_error_ = "Failed to find function '" + name + "': " + library_.getLastError();
}
return func;
}
// Get the last error message
std::string getLastError() const
{
return last_error_;
}
// Get the configuration being used
const CompilerConfig& getConfig() const
{
return config_;
}
// Check if a library is currently loaded
bool isLoaded() const
{
return library_.isLoaded();
}
// Get the path to compiled artifacts (object file, shared library, etc.)
// Only valid after successful compile() and if keep_artifacts is set
std::string getArtifactsPath() const
{
return temp_dir_;
}
// Get the cubin extracted during compilation
const std::vector<char>& getCubin() const
{
return cubin_;
}
// Unload the current library and clean up temporary files
void cleanup();
private:
std::string createTempDirectory();
void removeTempDirectory();
CompilerConfig config_;
CUDACompiler compiler_;
DynamicLibrary library_;
std::string temp_dir_;
std::string last_error_;
std::vector<char> cubin_;
};
} // namespace hostjit

View File

@@ -0,0 +1,47 @@
#pragma once
#include <string>
namespace hostjit
{
class DynamicLibrary
{
public:
DynamicLibrary();
~DynamicLibrary();
// Disable copy
DynamicLibrary(const DynamicLibrary&) = delete;
DynamicLibrary& operator=(const DynamicLibrary&) = delete;
// Enable move
DynamicLibrary(DynamicLibrary&& other) noexcept;
DynamicLibrary& operator=(DynamicLibrary&& other) noexcept;
// Load a shared library
bool load(const std::string& library_path);
// Get a symbol (function or variable) by name
void* getSymbol(const std::string& symbol_name);
// Template helper to get function pointers with type safety
template <typename FuncType>
FuncType getFunction(const std::string& name)
{
return reinterpret_cast<FuncType>(getSymbol(name));
}
// Check if library is loaded
bool isLoaded() const;
// Get the last error message
std::string getLastError() const;
// Unload the library
void unload();
private:
void* handle_;
std::string last_error_;
};
} // namespace hostjit

View File

@@ -0,0 +1,192 @@
#include <cstdlib>
#include <filesystem>
#include <iostream>
#include <random>
#include <sstream>
#include <hostjit/jit_compiler.hpp>
#ifdef _WIN32
# include <process.h>
#else
# include <unistd.h>
#endif
namespace hostjit
{
JITCompiler::JITCompiler()
: config_(detectDefaultConfig())
{}
JITCompiler::JITCompiler(const CompilerConfig& config)
: config_(config)
{}
JITCompiler::~JITCompiler()
{
cleanup();
}
bool JITCompiler::compile(const std::string& source_code)
{
std::string config_error;
if (!validateConfig(config_, &config_error))
{
last_error_ = "Configuration error: " + config_error;
return false;
}
cleanup();
temp_dir_ = createTempDirectory();
if (temp_dir_.empty())
{
last_error_ = "Failed to create temporary directory";
return false;
}
std::string obj_path = temp_dir_ + "/cuda_code.o";
auto compile_result = compiler_.compileToObject(source_code, obj_path, config_);
if (!compile_result.success)
{
last_error_ = "Compilation failed:\n" + compile_result.diagnostics;
removeTempDirectory();
return false;
}
// Store the cubin for later inspection
cubin_ = std::move(compile_result.cubin);
if (config_.verbose)
{
std::cout << "Compilation diagnostics:\n" << compile_result.diagnostics << "\n";
}
#ifdef _WIN32
std::string lib_path = temp_dir_ + "/cuda_code.dll";
#else
std::string lib_path = temp_dir_ + "/libcuda_code.so";
#endif
auto link_result = compiler_.linkToSharedLibrary({obj_path}, lib_path, config_);
if (!link_result.success)
{
last_error_ = "Linking failed:\n" + link_result.diagnostics;
removeTempDirectory();
return false;
}
if (config_.verbose)
{
std::cout << "Linking diagnostics:\n" << link_result.diagnostics << "\n";
}
if (!library_.load(lib_path))
{
last_error_ = "Failed to load library: " + library_.getLastError();
removeTempDirectory();
return false;
}
if (config_.verbose)
{
std::cout << "Successfully loaded library: " << lib_path << "\n";
}
last_error_.clear();
return true;
}
void JITCompiler::cleanup()
{
library_.unload();
if (!config_.keep_artifacts)
{
removeTempDirectory();
}
last_error_.clear();
}
std::string JITCompiler::createTempDirectory()
{
std::filesystem::path base_tmp_dir;
#ifdef _WIN32
const char* tmp_dir = std::getenv("TEMP");
if (!tmp_dir)
{
tmp_dir = std::getenv("TMP");
}
if (tmp_dir)
{
base_tmp_dir = tmp_dir;
}
else
{
base_tmp_dir = std::filesystem::temp_directory_path();
}
#else
const char* tmp_dir = std::getenv("TMPDIR");
if (tmp_dir)
{
base_tmp_dir = tmp_dir;
}
else
{
base_tmp_dir = "/tmp";
}
#endif
std::random_device rd;
std::mt19937 gen(rd());
std::uniform_int_distribution<> dis(0, 999999);
#ifdef _WIN32
int pid = _getpid();
#else
int pid = getpid();
#endif
for (int attempt = 0; attempt < 10; ++attempt)
{
std::string dir_name = "hostjit_" + std::to_string(pid) + "_" + std::to_string(dis(gen));
std::filesystem::path full_path = base_tmp_dir / dir_name;
std::error_code ec;
if (std::filesystem::create_directories(full_path, ec) && !ec)
{
return full_path.string();
}
}
return "";
}
void JITCompiler::removeTempDirectory()
{
if (temp_dir_.empty())
{
return;
}
try
{
if (std::filesystem::exists(temp_dir_))
{
std::filesystem::remove_all(temp_dir_);
}
}
catch (const std::filesystem::filesystem_error& e)
{
if (config_.verbose)
{
std::cerr << "Warning: Failed to remove temporary directory: " << e.what() << "\n";
}
}
temp_dir_.clear();
}
} // namespace hostjit

View File

@@ -0,0 +1,210 @@
#include <hostjit/loader.hpp>
#ifdef _WIN32
# define WIN32_LEAN_AND_MEAN
# include <windows.h>
#else
# include <dlfcn.h>
#endif
namespace hostjit
{
#ifdef _WIN32
namespace
{
// Run C++ static constructors in a DLL loaded with /NOENTRY /NODEFAULTLIB.
// The compiler places CUDA fatbin registration in the .CRT$XCU section.
// Without CRT startup, these never run, so we walk the merged .CRT section
// in the PE and call each non-null function pointer.
void runStaticInitializers(HMODULE module)
{
auto base = reinterpret_cast<const unsigned char*>(module);
auto dos = reinterpret_cast<const IMAGE_DOS_HEADER*>(base);
auto nt = reinterpret_cast<const IMAGE_NT_HEADERS*>(base + dos->e_lfanew);
auto sec = IMAGE_FIRST_SECTION(nt);
for (WORD i = 0; i < nt->FileHeader.NumberOfSections; ++i, ++sec)
{
if (memcmp(sec->Name, ".CRT", 4) == 0)
{
using InitFunc = void(__cdecl*)();
auto funcs = reinterpret_cast<InitFunc*>(const_cast<unsigned char*>(base) + sec->VirtualAddress);
size_t count = sec->SizeOfRawData / sizeof(InitFunc);
for (size_t j = 0; j < count; ++j)
{
if (funcs[j])
{
funcs[j]();
}
}
}
}
}
std::string getWindowsError()
{
DWORD error = GetLastError();
if (error == 0)
{
return "";
}
LPSTR buffer = nullptr;
DWORD size = FormatMessageA(
FORMAT_MESSAGE_ALLOCATE_BUFFER | FORMAT_MESSAGE_FROM_SYSTEM | FORMAT_MESSAGE_IGNORE_INSERTS,
nullptr,
error,
MAKELANGID(LANG_NEUTRAL, SUBLANG_DEFAULT),
reinterpret_cast<LPSTR>(&buffer),
0,
nullptr);
std::string message;
if (size > 0 && buffer)
{
message = std::string(buffer, size);
while (!message.empty() && (message.back() == '\n' || message.back() == '\r'))
{
message.pop_back();
}
LocalFree(buffer);
}
else
{
message = "Unknown error (code: " + std::to_string(error) + ")";
}
return message;
}
} // anonymous namespace
#endif
DynamicLibrary::DynamicLibrary()
: handle_(nullptr)
{}
DynamicLibrary::~DynamicLibrary()
{
unload();
}
DynamicLibrary::DynamicLibrary(DynamicLibrary&& other) noexcept
: handle_(other.handle_)
, last_error_(std::move(other.last_error_))
{
other.handle_ = nullptr;
}
DynamicLibrary& DynamicLibrary::operator=(DynamicLibrary&& other) noexcept
{
if (this != &other)
{
unload();
handle_ = other.handle_;
last_error_ = std::move(other.last_error_);
other.handle_ = nullptr;
}
return *this;
}
bool DynamicLibrary::load(const std::string& library_path)
{
unload();
#ifdef _WIN32
SetLastError(0);
handle_ = static_cast<void*>(LoadLibraryA(library_path.c_str()));
if (!handle_)
{
last_error_ = getWindowsError();
if (last_error_.empty())
{
last_error_ = "Unknown LoadLibrary error";
}
return false;
}
// The DLL is linked with /NOENTRY (no CRT startup), so C++ static
// constructors (e.g. CUDA fatbin registration) haven't run yet.
runStaticInitializers(static_cast<HMODULE>(handle_));
#else
dlerror();
handle_ = dlopen(library_path.c_str(), RTLD_LAZY | RTLD_LOCAL);
if (!handle_)
{
const char* error = dlerror();
last_error_ = error ? error : "Unknown dlopen error";
return false;
}
#endif
last_error_.clear();
return true;
}
void* DynamicLibrary::getSymbol(const std::string& symbol_name)
{
if (!handle_)
{
last_error_ = "Library not loaded";
return nullptr;
}
#ifdef _WIN32
SetLastError(0);
void* symbol = reinterpret_cast<void*>(GetProcAddress(static_cast<HMODULE>(handle_), symbol_name.c_str()));
if (!symbol)
{
last_error_ = getWindowsError();
if (last_error_.empty())
{
last_error_ = "Symbol not found: " + symbol_name;
}
return nullptr;
}
#else
dlerror();
void* symbol = dlsym(handle_, symbol_name.c_str());
const char* error = dlerror();
if (error)
{
last_error_ = error;
return nullptr;
}
#endif
last_error_.clear();
return symbol;
}
bool DynamicLibrary::isLoaded() const
{
return handle_ != nullptr;
}
std::string DynamicLibrary::getLastError() const
{
return last_error_;
}
void DynamicLibrary::unload()
{
if (handle_)
{
// Intentionally do NOT unload (dlclose / FreeLibrary) a compiled JIT module. See #9367.
//
// Each JIT .so is built by Clang with the classic fatbin embedding (-fcuda-include-gpubinary),
// which emits a module ctor (__cuda_module_ctor -> __cudaRegisterFatBinary)
// in .init_array but NO matching unregister dtor (.fini_array / __cudaUnregisterFatBinary).
// Unloading such a module unmaps its fatbin while the CUDA runtime still holds a pointer to it;
// that dangling registration corrupts the runtime's module table, so a later module's kernel
// launch silently no-ops.
handle_ = nullptr;
}
last_error_.clear();
}
} // namespace hostjit

View File

@@ -0,0 +1,222 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstring>
#include <cccl/c/merge_sort.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// Keys-only: (temp, temp_bytes, in_keys, out_keys, num_items, cmp_state, stream)
using keys_fn_t = int (*)(void*, size_t*, void*, void*, unsigned long long, void*, void*);
// Key-value pairs: (temp, temp_bytes, in_keys, in_items, out_keys, out_items, num_items, cmp_state, stream)
using pairs_fn_t = int (*)(void*, size_t*, void*, void*, void*, void*, unsigned long long, void*, void*);
static bool is_null_items(cccl_iterator_t it)
{
return it.type == CCCL_POINTER && it.state == nullptr;
}
// ---------------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------------
CUresult cccl_device_merge_sort_build_ex(
cccl_device_merge_sort_build_result_t* build_ptr,
cccl_iterator_t d_in_keys,
cccl_iterator_t d_in_items,
cccl_iterator_t d_out_keys,
cccl_iterator_t d_out_items,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (d_out_keys.type == CCCL_ITERATOR || d_out_items.type == CCCL_ITERATOR)
{
fprintf(stderr, "\nERROR in cccl_device_merge_sort_build(): merge sort output cannot be an iterator\n");
return CUDA_ERROR_UNKNOWN;
}
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
const bool has_items = !is_null_items(d_in_items);
CubCallResult result = [&] {
if (has_items)
{
return CubCall::from("cub/device/device_merge_sort.cuh")
.run("cub::DeviceMergeSort::SortPairsCopy")
.name("cccl_jit_merge_sort")
.with(temp_storage,
temp_bytes,
in(d_in_keys),
in(d_in_items),
out(d_out_keys),
out(d_out_items),
num_items,
cmp(op),
stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
}
else
{
return CubCall::from("cub/device/device_merge_sort.cuh")
.run("cub::DeviceMergeSort::SortKeysCopy")
.name("cccl_jit_merge_sort")
.with(temp_storage, temp_bytes, in(d_in_keys), out(d_out_keys), num_items, cmp(op), stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
}
}();
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->sort_fn = result.fn_ptr;
build_ptr->keys_only = has_items ? 0 : 1;
build_ptr->key_type = d_in_keys.value_type;
build_ptr->item_type = d_in_items.value_type;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_merge_sort_build(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_merge_sort_build(
cccl_device_merge_sort_build_result_t* build,
cccl_iterator_t d_in_keys,
cccl_iterator_t d_in_items,
cccl_iterator_t d_out_keys,
cccl_iterator_t d_out_items,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_merge_sort_build_ex(
build,
d_in_keys,
d_in_items,
d_out_keys,
d_out_items,
op,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
// ---------------------------------------------------------------------------
// Run
// ---------------------------------------------------------------------------
CUresult cccl_device_merge_sort(
cccl_device_merge_sort_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in_keys,
cccl_iterator_t d_in_items,
cccl_iterator_t d_out_keys,
cccl_iterator_t d_out_items,
uint64_t num_items,
cccl_op_t op,
CUstream stream)
try
{
if (!build.sort_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
int status;
// Dispatch to the correct function arity. build.keys_only was set at
// build time, so we don't have to re-derive the pairs-vs-keys decision
// from the iterator arguments here (and they must match the build for
// the function-pointer types to be valid).
if (!build.keys_only)
{
// Pairs build: (temp, temp_bytes, in_keys, in_items, out_keys, out_items, num_items, cmp_state, stream)
auto fn = reinterpret_cast<pairs_fn_t>(build.sort_fn);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_in_keys.state,
d_in_items.state,
d_out_keys.state,
d_out_items.state,
num_items,
op.state,
reinterpret_cast<void*>(stream));
}
else
{
// Keys-only build: (temp, temp_bytes, in_keys, out_keys, num_items, cmp_state, stream)
auto fn = reinterpret_cast<keys_fn_t>(build.sort_fn);
status =
fn(d_temp_storage,
temp_storage_bytes,
d_in_keys.state,
d_out_keys.state,
num_items,
op.state,
reinterpret_cast<void*>(stream));
}
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_merge_sort(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
CUresult cccl_device_merge_sort_cleanup(cccl_device_merge_sort_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->sort_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_merge_sort_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,356 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cccl/c/radix_sort.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
static bool is_null_it(cccl_iterator_t it)
{
return it.type == CCCL_POINTER && it.state == nullptr;
}
static bool is_null_op(cccl_op_t op)
{
return op.name == nullptr || op.name[0] == '\0';
}
// Two JIT wrappers are produced by CubCall per build:
//
// COPY variant — wraps cub::DeviceRadixSort::Sort{Keys,Pairs}{,Descending}'s
// copy-overload. Result is always in *_out; selector is implicitly 0. Used
// when the caller invokes the run-time API with is_overwrite_okay=false.
// keys-only: fn(temp, temp_bytes, keys_in, keys_out, num_items,
// &begin_bit, &end_bit, stream)
// pairs: fn(temp, temp_bytes, keys_in, keys_out, values_in, values_out,
// num_items, &begin_bit, &end_bit, stream)
//
// DOUBLE-BUFFER (overwrite) variant — wraps the DoubleBuffer overload.
// Constructs cub::DoubleBuffer<KeyT>(keys_in, keys_out) (and ValueT for
// pairs), runs the sort, then writes the buffer's `selector` (0 or 1) to a
// host-provided int*. Result may live in either keys_in or keys_out depending
// on the number of CUB passes — the selector tells the caller which.
// keys-only: fn(temp, temp_bytes, keys_in, keys_out, num_items,
// &begin_bit, &end_bit, selector_out, stream)
// pairs: fn(temp, temp_bytes, keys_in, keys_out, values_in, values_out,
// num_items, &begin_bit, &end_bit, selector_out, stream)
//
// begin_bit/end_bit go through CubCall::typed_scalar (host-pointer + memcpy
// onto the stack inside the JIT wrapper).
//
// Decomposer: only identity (null decomposer) is supported.
using radix_sort_keys_fn_t = int (*)(void*, size_t*, void*, void*, unsigned long long, void*, void*, void*);
using radix_sort_pairs_fn_t =
int (*)(void*, size_t*, void*, void*, void*, void*, unsigned long long, void*, void*, void*);
using radix_sort_keys_overwrite_fn_t =
int (*)(void*, size_t*, void*, void*, unsigned long long, void*, void*, void*, void*);
using radix_sort_pairs_overwrite_fn_t =
int (*)(void*, size_t*, void*, void*, void*, void*, unsigned long long, void*, void*, void*, void*);
// Type info for the begin_bit/end_bit int scalars passed to CubCall.
static constexpr cccl_type_info k_int_type{sizeof(int), alignof(int), CCCL_INT32};
CUresult cccl_device_radix_sort_build_ex(
cccl_device_radix_sort_build_result_t* build_ptr,
cccl_sort_order_t sort_order,
cccl_iterator_t input_keys_it,
cccl_iterator_t input_values_it,
cccl_op_t decomposer,
const char* /*decomposer_return_type*/,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (!is_null_op(decomposer))
{
fprintf(stderr,
"\nERROR in cccl_device_radix_sort_build(): custom radix decomposers are not supported "
"in the HostJIT path. Use standard integer/float key types.\n");
return CUDA_ERROR_UNKNOWN;
}
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
const bool keys_only = is_null_it(input_values_it);
const bool ascending = (sort_order == CCCL_ASCENDING);
// CUB writes to caller-provided device pointers at run time. Build needs
// an iterator descriptor for the outputs; synthesize raw-pointer ones with
// the same value_type as the matching input.
cccl_iterator_t output_keys_it = input_keys_it;
output_keys_it.type = CCCL_POINTER;
output_keys_it.state = nullptr;
cccl_iterator_t output_values_it{};
output_values_it.type = CCCL_POINTER;
output_values_it.state = nullptr;
output_values_it.value_type = input_values_it.value_type;
const char* cub_algo;
if (keys_only)
{
cub_algo = ascending ? "cub::DeviceRadixSort::SortKeys" : "cub::DeviceRadixSort::SortKeysDescending";
}
else
{
cub_algo = ascending ? "cub::DeviceRadixSort::SortPairs" : "cub::DeviceRadixSort::SortPairsDescending";
}
auto cb_copy = [&] {
if (keys_only)
{
return CubCall::from("cub/device/device_radix_sort.cuh")
.run(cub_algo)
.name("cccl_jit_radix_sort")
.with(temp_storage,
temp_bytes,
in(input_keys_it),
out(output_keys_it),
num_items,
typed_scalar(k_int_type, "begin_bit"),
typed_scalar(k_int_type, "end_bit"),
stream);
}
return CubCall::from("cub/device/device_radix_sort.cuh")
.run(cub_algo)
.name("cccl_jit_radix_sort")
.with(temp_storage,
temp_bytes,
in(input_keys_it),
out(output_keys_it),
in(input_values_it),
out(output_values_it),
num_items,
typed_scalar(k_int_type, "begin_bit"),
typed_scalar(k_int_type, "end_bit"),
stream);
}();
auto cb_overwrite = [&] {
if (keys_only)
{
return CubCall::from("cub/device/device_radix_sort.cuh")
.run(cub_algo)
.name("cccl_jit_radix_sort_overwrite")
.with(temp_storage,
temp_bytes,
double_buffer(input_keys_it, output_keys_it, "d_keys_buffer"),
num_items,
typed_scalar(k_int_type, "begin_bit"),
typed_scalar(k_int_type, "end_bit"),
selector_out("d_keys_buffer"),
stream);
}
return CubCall::from("cub/device/device_radix_sort.cuh")
.run(cub_algo)
.name("cccl_jit_radix_sort_overwrite")
.with(temp_storage,
temp_bytes,
double_buffer(input_keys_it, output_keys_it, "d_keys_buffer"),
double_buffer(input_values_it, output_values_it, "d_values_buffer"),
num_items,
typed_scalar(k_int_type, "begin_bit"),
typed_scalar(k_int_type, "end_bit"),
selector_out("d_keys_buffer"),
stream);
}();
auto result =
CubCall::compile({cb_copy, cb_overwrite}, cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->sort_fn = result.fn_ptrs[0];
build_ptr->sort_fn_overwrite = result.fn_ptrs[1];
build_ptr->key_type = input_keys_it.value_type;
build_ptr->value_type = input_values_it.value_type;
build_ptr->order = sort_order;
build_ptr->keys_only = keys_only ? 1 : 0;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_radix_sort_build(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_radix_sort_build(
cccl_device_radix_sort_build_result_t* build,
cccl_sort_order_t sort_order,
cccl_iterator_t input_keys_it,
cccl_iterator_t input_values_it,
cccl_op_t decomposer,
const char* decomposer_return_type,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_radix_sort_build_ex(
build,
sort_order,
input_keys_it,
input_values_it,
decomposer,
decomposer_return_type,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
CUresult cccl_device_radix_sort(
cccl_device_radix_sort_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_in,
cccl_iterator_t d_values_out,
cccl_op_t /*decomposer*/,
uint64_t num_items,
int begin_bit,
int end_bit,
bool is_overwrite_okay,
int* selector,
CUstream stream)
try
{
// Dispatch on is_overwrite_okay: the copy variant always lands the result
// in d_keys_out (selector = 0); the DoubleBuffer variant may land it in
// either buffer and reports which via its `selector` member, captured here
// by passing a pointer for the wrapper to write into.
int status;
int local_selector = 0;
if (is_overwrite_okay)
{
if (!build.sort_fn_overwrite)
{
return CUDA_ERROR_INVALID_VALUE;
}
if (build.keys_only)
{
auto fn = reinterpret_cast<radix_sort_keys_overwrite_fn_t>(build.sort_fn_overwrite);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
static_cast<unsigned long long>(num_items),
&begin_bit,
&end_bit,
&local_selector,
reinterpret_cast<void*>(stream));
}
else
{
auto fn = reinterpret_cast<radix_sort_pairs_overwrite_fn_t>(build.sort_fn_overwrite);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
d_values_in.state,
d_values_out.state,
static_cast<unsigned long long>(num_items),
&begin_bit,
&end_bit,
&local_selector,
reinterpret_cast<void*>(stream));
}
}
else
{
if (!build.sort_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
if (build.keys_only)
{
auto fn = reinterpret_cast<radix_sort_keys_fn_t>(build.sort_fn);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
static_cast<unsigned long long>(num_items),
&begin_bit,
&end_bit,
reinterpret_cast<void*>(stream));
}
else
{
auto fn = reinterpret_cast<radix_sort_pairs_fn_t>(build.sort_fn);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
d_values_in.state,
d_values_out.state,
static_cast<unsigned long long>(num_items),
&begin_bit,
&end_bit,
reinterpret_cast<void*>(stream));
}
local_selector = 0;
}
if (selector)
{
*selector = local_selector;
}
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_radix_sort(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_radix_sort_cleanup(cccl_device_radix_sort_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->sort_fn = nullptr;
build_ptr->sort_fn_overwrite = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_radix_sort_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,167 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstring>
#include <filesystem>
#include <string>
#include <cccl/c/reduce.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// (temp_storage, temp_bytes, d_in, d_out, num_items, op_state, init_state, stream)
using reduce_fn_t = int (*)(void*, size_t*, void*, void*, unsigned long long, void*, void*, void*);
CUresult cccl_device_reduce_build_ex(
cccl_device_reduce_build_result_t* build,
cccl_iterator_t input_it,
cccl_iterator_t output_it,
cccl_op_t op,
cccl_value_t init,
cccl_determinism_t determinism,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* build_config)
try
{
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(build_config, cub_path, thrust_path);
auto result =
CubCall::from("cub/device/device_reduce.cuh")
.run("cub::DeviceReduce::Reduce")
.name("cccl_jit_reduce")
.with(temp_storage, temp_bytes, in(input_it), out(output_it), num_items, op, init, stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build->payload, build->payload_size);
build->jit_compiler = result.compiler;
build->reduce_fn = reinterpret_cast<void*>(result.fn_ptr);
build->accumulator_size = init.type.size;
build->determinism = determinism;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_reduce_build(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_reduce(
cccl_device_reduce_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream)
try
{
if (!build.reduce_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
auto reduce_fn = reinterpret_cast<reduce_fn_t>(build.reduce_fn);
// Parameter order matches CubCall::with() order: ..., num_items, op.state, init.state, stream
const int status = reduce_fn(
d_temp_storage,
temp_storage_bytes,
d_in.state,
d_out.state,
num_items,
op.state,
init.state,
reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_reduce(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_reduce_nondeterministic(
cccl_device_reduce_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream)
{
return cccl_device_reduce(build, d_temp_storage, temp_storage_bytes, d_in, d_out, num_items, op, init, stream);
}
CUresult cccl_device_reduce_cleanup(cccl_device_reduce_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->reduce_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_reduce_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_reduce_build(
cccl_device_reduce_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
cccl_value_t init,
cccl_determinism_t determinism,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_reduce_build_ex(
build,
d_in,
d_out,
op,
init,
determinism,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}

View File

@@ -0,0 +1,319 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstring>
#include <cccl/c/scan.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// Variants with an init value (value or future): 8 args
// (temp, temp_bytes, d_in, d_out, op_state, init_ptr, num_items, stream)
using scan_init_fn_t = int (*)(void*, size_t*, void*, void*, void*, void*, unsigned long long, void*);
// InclusiveScan without init: 7 args
// (temp, temp_bytes, d_in, d_out, op_state, num_items, stream)
using scan_no_init_fn_t = int (*)(void*, size_t*, void*, void*, void*, unsigned long long, void*);
// ---------------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------------
CUresult cccl_device_scan_build_ex(
cccl_device_scan_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
cccl_type_info init_type,
bool force_inclusive,
cccl_init_kind_t init_kind,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
CubCallResult result = [&] {
auto base = CubCall::from("cub/device/device_scan.cuh").name("cccl_jit_scan");
if (init_kind == CCCL_NO_INIT)
{
// cub::DeviceScan::InclusiveScan(temp, temp_bytes, in, out, op, num_items, stream)
return base.run("cub::DeviceScan::InclusiveScan")
.with(temp_storage, temp_bytes, in(d_in), out(d_out), op, num_items, stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
}
else if (init_kind == CCCL_VALUE_INIT)
{
// ExclusiveScan or InclusiveScanInit with a value init (memcpy'd from void*)
const char* fn = force_inclusive ? "cub::DeviceScan::InclusiveScanInit" : "cub::DeviceScan::ExclusiveScan";
cccl_value_t init_val{init_type, nullptr}; // state=nullptr; passed at run time
return base.run(fn)
.with(temp_storage, temp_bytes, in(d_in), out(d_out), op, init_val, num_items, stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
}
else // CCCL_FUTURE_VALUE_INIT
{
// ExclusiveScan or InclusiveScanInit with cub::FutureValue<accum_t>(ptr)
const char* fn = force_inclusive ? "cub::DeviceScan::InclusiveScanInit" : "cub::DeviceScan::ExclusiveScan";
return base.run(fn)
.with(temp_storage, temp_bytes, in(d_in), out(d_out), op, future_val(init_type), num_items, stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
}
}();
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->scan_fn = result.fn_ptr;
build_ptr->force_inclusive = force_inclusive;
build_ptr->init_kind = init_kind;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_scan_build_ex(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_scan_build(
cccl_device_scan_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
cccl_type_info init_type,
bool force_inclusive,
cccl_init_kind_t init_kind,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_scan_build_ex(
build_ptr,
d_in,
d_out,
op,
init_type,
force_inclusive,
init_kind,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
// ---------------------------------------------------------------------------
// Run helpers
// ---------------------------------------------------------------------------
static CUresult call_scan_init(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
void* init_ptr, // value state or device pointer for FutureValue
CUstream stream)
{
if (!build.scan_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
// Guard against ABI mismatch: this path uses the 8-arg scan_init_fn_t
// (with init pointer). Calling it with a build result compiled for
// CCCL_NO_INIT (7-arg scan_no_init_fn_t) would be undefined behaviour.
if (build.init_kind == CCCL_NO_INIT)
{
return CUDA_ERROR_INVALID_VALUE;
}
auto fn = reinterpret_cast<scan_init_fn_t>(build.scan_fn);
const int status = fn(
d_temp_storage,
temp_storage_bytes,
d_in.state,
d_out.state,
op.state,
init_ptr,
(unsigned long long) num_items,
reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
// ---------------------------------------------------------------------------
// Run
// ---------------------------------------------------------------------------
CUresult cccl_device_exclusive_scan(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream)
try
{
return call_scan_init(build, d_temp_storage, temp_storage_bytes, d_in, d_out, num_items, op, init.state, stream);
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_exclusive_scan(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_inclusive_scan(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_value_t init,
CUstream stream)
try
{
return call_scan_init(build, d_temp_storage, temp_storage_bytes, d_in, d_out, num_items, op, init.state, stream);
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_inclusive_scan(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_exclusive_scan_future_value(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_iterator_t init,
CUstream stream)
try
{
// init.state is the device pointer — passed as void* and wrapped in
// FutureValue<accum_t> inside the compiled CUDA function.
return call_scan_init(build, d_temp_storage, temp_storage_bytes, d_in, d_out, num_items, op, init.state, stream);
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_exclusive_scan_future_value(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_inclusive_scan_future_value(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
cccl_iterator_t init,
CUstream stream)
try
{
return call_scan_init(build, d_temp_storage, temp_storage_bytes, d_in, d_out, num_items, op, init.state, stream);
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_inclusive_scan_future_value(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_inclusive_scan_no_init(
cccl_device_scan_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
CUstream stream)
try
{
if (!build.scan_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
// Guard against ABI mismatch: this path uses the 7-arg scan_no_init_fn_t.
// A build result compiled with an init value stores an 8-arg scan_init_fn_t;
// casting it here would be undefined behaviour.
if (build.init_kind != CCCL_NO_INIT)
{
return CUDA_ERROR_INVALID_VALUE;
}
auto fn = reinterpret_cast<scan_no_init_fn_t>(build.scan_fn);
const int status =
fn(d_temp_storage,
temp_storage_bytes,
d_in.state,
d_out.state,
op.state,
(unsigned long long) num_items,
reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_inclusive_scan_no_init(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
CUresult cccl_device_scan_cleanup(cccl_device_scan_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->scan_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_scan_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,169 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstring>
#include <string>
#include <cccl/c/segmented_reduce.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// (temp_storage, temp_bytes, d_in, d_out, num_segments, begin_offsets, end_offsets, op, init, stream)
using segmented_reduce_fn_t =
int (*)(void*, size_t*, void*, void*, unsigned long long, void*, void*, void*, void*, void*);
CUresult cccl_device_segmented_reduce_build_ex(
cccl_device_segmented_reduce_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_iterator_t start_offset_it,
cccl_iterator_t end_offset_it,
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,
cccl_build_config* build_config)
try
{
const std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(build_config, cub_path, thrust_path);
auto result =
CubCall::from("cub/device/device_segmented_reduce.cuh")
.run("cub::DeviceSegmentedReduce::Reduce")
.name("cccl_jit_segmented_reduce")
.with(temp_storage,
temp_bytes,
in(d_in),
out(d_out),
num_items,
in(start_offset_it),
in(end_offset_it),
op,
init,
stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build->payload, build->payload_size);
build->jit_compiler = result.compiler;
build->segmented_reduce_fn = reinterpret_cast<void*>(result.fn_ptr);
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_segmented_reduce_build(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_segmented_reduce(
cccl_device_segmented_reduce_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_segments,
cccl_iterator_t start_offset,
cccl_iterator_t end_offset,
cccl_op_t op,
cccl_value_t init,
CUstream stream)
try
{
if (!build.segmented_reduce_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
auto segmented_reduce_fn = reinterpret_cast<segmented_reduce_fn_t>(build.segmented_reduce_fn);
// Parameter order matches CubCall::with() order:
// temp_storage, temp_bytes, d_in, d_out, num_items, begin_offsets, end_offsets, op, init, stream
const int status = segmented_reduce_fn(
d_temp_storage,
temp_storage_bytes,
d_in.state,
d_out.state,
num_segments,
start_offset.state,
end_offset.state,
op.state,
init.state,
reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_segmented_reduce(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_segmented_reduce_build(
cccl_device_segmented_reduce_build_result_t* build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
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)
{
return cccl_device_segmented_reduce_build_ex(
build,
d_in,
d_out,
begin_offset_in,
end_offset_in,
op,
init,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
CUresult cccl_device_segmented_reduce_cleanup(cccl_device_segmented_reduce_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->segmented_reduce_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_segmented_reduce_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,336 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cccl/c/segmented_sort.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
static bool is_null_it(cccl_iterator_t it)
{
return it.type == CCCL_POINTER && it.state == nullptr;
}
// Two JIT wrappers per build, one per cub::DeviceSegmentedSort overload:
//
// COPY variant — result always lands in d_keys_out (and d_values_out for
// pairs); selector implicitly 0. Used when is_overwrite_okay=false.
// keys-only: fn(temp, temp_bytes, keys_in, keys_out, num_items, num_segments,
// begin_offsets, end_offsets, stream)
// pairs: fn(temp, temp_bytes, keys_in, keys_out, values_in, values_out,
// num_items, num_segments, begin_offsets, end_offsets, stream)
//
// DOUBLE-BUFFER (overwrite) variant — constructs cub::DoubleBuffer locals
// from the in/out pointers, runs the DoubleBuffer overload, writes the
// buffer's selector to a host-provided int*.
// keys-only: fn(..., num_segments, begin_offsets, end_offsets, selector_out, stream)
// pairs: fn(..., num_segments, begin_offsets, end_offsets, selector_out, stream)
using segmented_sort_keys_fn_t =
int (*)(void*, size_t*, void*, void*, unsigned long long, unsigned long long, void*, void*, void*);
using segmented_sort_pairs_fn_t =
int (*)(void*, size_t*, void*, void*, void*, void*, unsigned long long, unsigned long long, void*, void*, void*);
using segmented_sort_keys_overwrite_fn_t =
int (*)(void*, size_t*, void*, void*, unsigned long long, unsigned long long, void*, void*, void*, void*);
using segmented_sort_pairs_overwrite_fn_t = int (*)(
void*, size_t*, void*, void*, void*, void*, unsigned long long, unsigned long long, void*, void*, void*, void*);
CUresult cccl_device_segmented_sort_build_ex(
cccl_device_segmented_sort_build_result_t* build_ptr,
cccl_sort_order_t sort_order,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
const bool keys_only = is_null_it(d_values_in);
const bool ascending = (sort_order == CCCL_ASCENDING);
// DeviceSegmentedSort writes to caller-provided device pointers at run time.
// Build needs an iterator descriptor for the outputs; synthesize raw-pointer
// ones with the same value_type as the matching inputs.
cccl_iterator_t d_keys_out = d_keys_in;
d_keys_out.type = CCCL_POINTER;
d_keys_out.state = nullptr;
cccl_iterator_t d_values_out{};
d_values_out.type = CCCL_POINTER;
d_values_out.state = nullptr;
d_values_out.value_type = d_values_in.value_type;
const char* cub_algo;
if (keys_only)
{
cub_algo = ascending ? "cub::DeviceSegmentedSort::SortKeys" : "cub::DeviceSegmentedSort::SortKeysDescending";
}
else
{
cub_algo = ascending ? "cub::DeviceSegmentedSort::SortPairs" : "cub::DeviceSegmentedSort::SortPairsDescending";
}
auto cb_copy = [&] {
if (keys_only)
{
return CubCall::from("cub/device/device_segmented_sort.cuh")
.run(cub_algo)
.name("cccl_jit_segmented_sort")
.with(temp_storage,
temp_bytes,
in(d_keys_in),
out(d_keys_out),
num_items,
num_segments,
in(begin_offset_in),
in(end_offset_in),
stream);
}
return CubCall::from("cub/device/device_segmented_sort.cuh")
.run(cub_algo)
.name("cccl_jit_segmented_sort")
.with(temp_storage,
temp_bytes,
in(d_keys_in),
out(d_keys_out),
in(d_values_in),
out(d_values_out),
num_items,
num_segments,
in(begin_offset_in),
in(end_offset_in),
stream);
}();
auto cb_overwrite = [&] {
if (keys_only)
{
return CubCall::from("cub/device/device_segmented_sort.cuh")
.run(cub_algo)
.name("cccl_jit_segmented_sort_overwrite")
.with(temp_storage,
temp_bytes,
double_buffer(d_keys_in, d_keys_out, "d_keys_buffer"),
num_items,
num_segments,
in(begin_offset_in),
in(end_offset_in),
selector_out("d_keys_buffer"),
stream);
}
return CubCall::from("cub/device/device_segmented_sort.cuh")
.run(cub_algo)
.name("cccl_jit_segmented_sort_overwrite")
.with(temp_storage,
temp_bytes,
double_buffer(d_keys_in, d_keys_out, "d_keys_buffer"),
double_buffer(d_values_in, d_values_out, "d_values_buffer"),
num_items,
num_segments,
in(begin_offset_in),
in(end_offset_in),
selector_out("d_keys_buffer"),
stream);
}();
auto result =
CubCall::compile({cb_copy, cb_overwrite}, cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->sort_fn = result.fn_ptrs[0];
build_ptr->sort_fn_overwrite = result.fn_ptrs[1];
build_ptr->key_type = d_keys_in.value_type;
build_ptr->value_type = d_values_in.value_type;
build_ptr->order = sort_order;
build_ptr->keys_only = keys_only ? 1 : 0;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_segmented_sort_build(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_segmented_sort_build(
cccl_device_segmented_sort_build_result_t* build,
cccl_sort_order_t sort_order,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t begin_offset_in,
cccl_iterator_t end_offset_in,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_segmented_sort_build_ex(
build,
sort_order,
d_keys_in,
d_values_in,
begin_offset_in,
end_offset_in,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
CUresult cccl_device_segmented_sort(
cccl_device_segmented_sort_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_in,
cccl_iterator_t d_values_out,
uint64_t num_items,
uint64_t num_segments,
cccl_iterator_t start_offset_in,
cccl_iterator_t end_offset_in,
bool is_overwrite_okay,
int* selector,
CUstream stream)
try
{
// Dispatch on is_overwrite_okay (see analogous comment in radix_sort.cu).
int status;
int local_selector = 0;
if (is_overwrite_okay)
{
if (!build.sort_fn_overwrite)
{
return CUDA_ERROR_INVALID_VALUE;
}
if (build.keys_only)
{
auto fn = reinterpret_cast<segmented_sort_keys_overwrite_fn_t>(build.sort_fn_overwrite);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
static_cast<unsigned long long>(num_items),
static_cast<unsigned long long>(num_segments),
start_offset_in.state,
end_offset_in.state,
&local_selector,
reinterpret_cast<void*>(stream));
}
else
{
auto fn = reinterpret_cast<segmented_sort_pairs_overwrite_fn_t>(build.sort_fn_overwrite);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
d_values_in.state,
d_values_out.state,
static_cast<unsigned long long>(num_items),
static_cast<unsigned long long>(num_segments),
start_offset_in.state,
end_offset_in.state,
&local_selector,
reinterpret_cast<void*>(stream));
}
}
else
{
if (!build.sort_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
if (build.keys_only)
{
auto fn = reinterpret_cast<segmented_sort_keys_fn_t>(build.sort_fn);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
static_cast<unsigned long long>(num_items),
static_cast<unsigned long long>(num_segments),
start_offset_in.state,
end_offset_in.state,
reinterpret_cast<void*>(stream));
}
else
{
auto fn = reinterpret_cast<segmented_sort_pairs_fn_t>(build.sort_fn);
status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_keys_out.state,
d_values_in.state,
d_values_out.state,
static_cast<unsigned long long>(num_items),
static_cast<unsigned long long>(num_segments),
start_offset_in.state,
end_offset_in.state,
reinterpret_cast<void*>(stream));
}
local_selector = 0;
}
if (selector)
{
*selector = local_selector;
}
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_segmented_sort(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_segmented_sort_cleanup(cccl_device_segmented_sort_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->sort_fn = nullptr;
build_ptr->sort_fn_overwrite = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_segmented_sort_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,193 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstring>
#include <cccl/c/three_way_partition.h>
#include <hostjit/codegen/cub_call.hpp>
#include <hostjit/jit_compiler.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// CUB DevicePartition::If (three-way) generated signature:
// (temp, bytes, d_in, first_out, second_out, unselected_out, num_selected_out,
// num_items, first_op_state, second_op_state, stream)
using three_way_partition_fn_t =
int (*)(void*, size_t*, void*, void*, void*, void*, void*, unsigned long long, void*, void*, void*);
// ---------------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------------
CUresult cccl_device_three_way_partition_build_ex(
cccl_device_three_way_partition_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_first_part_out,
cccl_iterator_t d_second_part_out,
cccl_iterator_t d_unselected_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t select_first_part_op,
cccl_op_t select_second_part_op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
// DevicePartition::If (three-way):
// (temp, bytes, d_in, d_first_part_out, d_second_part_out, d_unselected_out,
// d_num_selected_out, num_items, select_first_op, select_second_op, stream)
auto result =
CubCall::from("cub/device/device_partition.cuh")
.run("cub::DevicePartition::If")
.name("cccl_jit_three_way_partition")
.with(temp_storage,
temp_bytes,
in(d_in),
out(d_first_part_out),
out(d_second_part_out),
out(d_unselected_out),
out(d_num_selected_out),
num_items,
pred(select_first_part_op, d_in.value_type),
pred(select_second_part_op, d_in.value_type),
stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->three_way_partition_fn = result.fn_ptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_three_way_partition_build_ex(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_three_way_partition_build(
cccl_device_three_way_partition_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_first_part_out,
cccl_iterator_t d_second_part_out,
cccl_iterator_t d_unselected_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t select_first_part_op,
cccl_op_t select_second_part_op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_three_way_partition_build_ex(
build_ptr,
d_in,
d_first_part_out,
d_second_part_out,
d_unselected_out,
d_num_selected_out,
select_first_part_op,
select_second_part_op,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
// ---------------------------------------------------------------------------
// Run
// ---------------------------------------------------------------------------
CUresult cccl_device_three_way_partition(
cccl_device_three_way_partition_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_in,
cccl_iterator_t d_first_part_out,
cccl_iterator_t d_second_part_out,
cccl_iterator_t d_unselected_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t select_first_part_op,
cccl_op_t select_second_part_op,
uint64_t num_items,
CUstream stream)
try
{
if (!build.three_way_partition_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
auto fn = reinterpret_cast<three_way_partition_fn_t>(build.three_way_partition_fn);
const int status = fn(
d_temp_storage,
temp_storage_bytes,
d_in.state,
d_first_part_out.state,
d_second_part_out.state,
d_unselected_out.state,
d_num_selected_out.state,
static_cast<unsigned long long>(num_items),
select_first_part_op.state,
select_second_part_op.state,
reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_three_way_partition(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
CUresult cccl_device_three_way_partition_cleanup(cccl_device_three_way_partition_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->three_way_partition_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_three_way_partition_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,283 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cuda/std/version>
#include <cstdio>
#include <cstring>
#include <memory>
#include <cccl/c/transform.h>
#include <hostjit/codegen/cub_call.hpp>
#include <util/build_utils.h>
#include <util/first_call_gate.h>
using namespace hostjit::codegen;
// (d_in, d_out, num_items, op_state, stream)
using unary_transform_fn_t = int (*)(void*, void*, unsigned long long, void*, void*);
// (d_in1, d_in2, d_out, num_items, op_state, stream)
using binary_transform_fn_t = int (*)(void*, void*, void*, unsigned long long, void*, void*);
// ---------------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------------
CUresult cccl_device_unary_transform_build_ex(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
#if CCCL_OS(WINDOWS)
build_ptr->first_call_state = nullptr;
#endif
const std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
const std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* const cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* const ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
#if CCCL_OS(WINDOWS)
auto first_call_state = std::make_unique<cccl::detail::first_call_gate>();
#endif
auto result =
CubCall::from("cub/device/device_transform.cuh")
.run("cub::DeviceTransform::Transform")
.name("cccl_jit_unary_transform")
.with(in(d_in), out(d_out), num_items, unary_op(op, d_in.value_type, d_out.value_type), stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
#if CCCL_OS(WINDOWS)
build_ptr->first_call_state = first_call_state.release();
#endif
build_ptr->transform_fn = result.fn_ptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_unary_transform_build_ex(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_binary_transform_build_ex(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in1,
cccl_iterator_t d_in2,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
#if CCCL_OS(WINDOWS)
build_ptr->first_call_state = nullptr;
#endif
const std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
const std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* const cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* const ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
#if CCCL_OS(WINDOWS)
auto first_call_state = std::make_unique<cccl::detail::first_call_gate>();
#endif
// Use the output type as the accumulator type (same as the previous raw JIT
// implementation) so the binary op functor uses the correct result type.
auto result =
CubCall::from("cub/device/device_transform.cuh")
.run("cub::DeviceTransform::Transform")
.name("cccl_jit_binary_transform")
.use_tuple_inputs()
.with(force_accum_type(d_out.value_type), in(d_in1), in(d_in2), out(d_out), num_items, op, stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
#if CCCL_OS(WINDOWS)
build_ptr->first_call_state = first_call_state.release();
#endif
build_ptr->transform_fn = result.fn_ptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_binary_transform_build_ex(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
// ---------------------------------------------------------------------------
// Non-ex wrappers (call _ex with nullptr config)
// ---------------------------------------------------------------------------
CUresult cccl_device_unary_transform_build(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_unary_transform_build_ex(
build_ptr, d_in, d_out, op, cc_major, cc_minor, cub_path, thrust_path, libcudacxx_path, ctk_path, nullptr);
}
CUresult cccl_device_binary_transform_build(
cccl_device_transform_build_result_t* build_ptr,
cccl_iterator_t d_in1,
cccl_iterator_t d_in2,
cccl_iterator_t d_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_binary_transform_build_ex(
build_ptr, d_in1, d_in2, d_out, op, cc_major, cc_minor, cub_path, thrust_path, libcudacxx_path, ctk_path, nullptr);
}
// ---------------------------------------------------------------------------
// Runtime functions
// ---------------------------------------------------------------------------
CUresult cccl_device_unary_transform(
cccl_device_transform_build_result_t build,
cccl_iterator_t d_in,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
CUstream stream)
try
{
if (!build.transform_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
const auto fn = reinterpret_cast<unary_transform_fn_t>(build.transform_fn);
#if CCCL_OS(WINDOWS)
if (!build.first_call_state)
{
return CUDA_ERROR_INVALID_VALUE;
}
const auto invoke = [&] {
return fn(d_in.state, d_out.state, num_items, op.state, reinterpret_cast<void*>(stream));
};
// Empty calls return before CUB initializes its static launch configuration,
// so they must not complete the first-call gate.
const int status =
num_items == 0 ? invoke() : static_cast<cccl::detail::first_call_gate*>(build.first_call_state)->invoke(invoke);
#else
const int status = fn(d_in.state, d_out.state, num_items, op.state, reinterpret_cast<void*>(stream));
#endif
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_unary_transform(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_binary_transform(
cccl_device_transform_build_result_t build,
cccl_iterator_t d_in1,
cccl_iterator_t d_in2,
cccl_iterator_t d_out,
uint64_t num_items,
cccl_op_t op,
CUstream stream)
try
{
if (!build.transform_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
const auto fn = reinterpret_cast<binary_transform_fn_t>(build.transform_fn);
#if CCCL_OS(WINDOWS)
if (!build.first_call_state)
{
return CUDA_ERROR_INVALID_VALUE;
}
const auto invoke = [&] {
return fn(d_in1.state, d_in2.state, d_out.state, num_items, op.state, reinterpret_cast<void*>(stream));
};
// Empty calls return before CUB initializes its static launch configuration,
// so they must not complete the first-call gate.
const int status =
num_items == 0 ? invoke() : static_cast<cccl::detail::first_call_gate*>(build.first_call_state)->invoke(invoke);
#else
const int status = fn(d_in1.state, d_in2.state, d_out.state, num_items, op.state, reinterpret_cast<void*>(stream));
#endif
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_binary_transform(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
CUresult cccl_device_transform_cleanup(cccl_device_transform_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
#if CCCL_OS(WINDOWS)
delete static_cast<cccl::detail::first_call_gate*>(build_ptr->first_call_state);
build_ptr->first_call_state = nullptr;
#endif
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->transform_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_transform_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,184 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#include <cstdio>
#include <cstring>
#include <cccl/c/unique_by_key.h>
#include <hostjit/codegen/cub_call.hpp>
#include <hostjit/jit_compiler.hpp>
#include <util/build_utils.h>
using namespace hostjit::codegen;
// CUB DeviceSelect::UniqueByKey generated signature:
// (temp, bytes, keys_in, values_in, keys_out, values_out, num_selected_out, num_items, cmp_state, stream)
using unique_by_key_fn_t = int (*)(void*, size_t*, void*, void*, void*, void*, void*, unsigned long long, void*, void*);
// ---------------------------------------------------------------------------
// Build
// ---------------------------------------------------------------------------
CUresult cccl_device_unique_by_key_build_ex(
cccl_device_unique_by_key_build_result_t* build_ptr,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path,
cccl_build_config* config)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
std::string cccl_include_str = cccl::detail::parse_cccl_include_path(libcudacxx_path);
std::string ctk_root_str = cccl::detail::parse_ctk_root(ctk_path);
const char* cccl_include_path = cccl_include_str.empty() ? nullptr : cccl_include_str.c_str();
const char* ctk_root = ctk_root_str.empty() ? nullptr : ctk_root_str.c_str();
cccl::detail::MergedBuildConfig merged(config, cub_path, thrust_path);
// DeviceSelect::UniqueByKey(temp, bytes, keys_in, values_in, keys_out, values_out,
// num_selected_out, num_items, equality_op, stream)
auto result =
CubCall::from("cub/device/device_select.cuh")
.run("cub::DeviceSelect::UniqueByKey")
.name("cccl_jit_unique_by_key")
.with(temp_storage,
temp_bytes,
in(d_keys_in),
in(d_values_in),
out(d_keys_out),
out(d_values_out),
out(d_num_selected_out),
num_items,
cmp(op),
stream)
.compile(cc_major, cc_minor, merged.get(), ctk_root, cccl_include_path);
build_ptr->cc = cc_major * 10 + cc_minor;
cccl::detail::copy_cubin(result.cubin, build_ptr->payload, build_ptr->payload_size);
build_ptr->jit_compiler = result.compiler;
build_ptr->unique_by_key_fn = result.fn_ptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_unique_by_key_build_ex(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
CUresult cccl_device_unique_by_key_build(
cccl_device_unique_by_key_build_result_t* build,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t op,
int cc_major,
int cc_minor,
const char* cub_path,
const char* thrust_path,
const char* libcudacxx_path,
const char* ctk_path)
{
return cccl_device_unique_by_key_build_ex(
build,
d_keys_in,
d_values_in,
d_keys_out,
d_values_out,
d_num_selected_out,
op,
cc_major,
cc_minor,
cub_path,
thrust_path,
libcudacxx_path,
ctk_path,
nullptr);
}
// ---------------------------------------------------------------------------
// Run
// ---------------------------------------------------------------------------
CUresult cccl_device_unique_by_key(
cccl_device_unique_by_key_build_result_t build,
void* d_temp_storage,
size_t* temp_storage_bytes,
cccl_iterator_t d_keys_in,
cccl_iterator_t d_values_in,
cccl_iterator_t d_keys_out,
cccl_iterator_t d_values_out,
cccl_iterator_t d_num_selected_out,
cccl_op_t op,
uint64_t num_items,
CUstream stream)
try
{
if (!build.unique_by_key_fn)
{
return CUDA_ERROR_INVALID_VALUE;
}
auto fn = reinterpret_cast<unique_by_key_fn_t>(build.unique_by_key_fn);
const int status = fn(
d_temp_storage,
temp_storage_bytes,
d_keys_in.state,
d_values_in.state,
d_keys_out.state,
d_values_out.state,
d_num_selected_out.state,
static_cast<unsigned long long>(num_items),
op.state,
reinterpret_cast<void*>(stream));
return (status == 0) ? CUDA_SUCCESS : CUDA_ERROR_UNKNOWN;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_unique_by_key(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}
// ---------------------------------------------------------------------------
// Cleanup
// ---------------------------------------------------------------------------
CUresult cccl_device_unique_by_key_cleanup(cccl_device_unique_by_key_build_result_t* build_ptr)
try
{
if (build_ptr == nullptr)
{
return CUDA_ERROR_INVALID_VALUE;
}
cccl::detail::release_jit_artifacts(build_ptr);
build_ptr->unique_by_key_fn = nullptr;
return CUDA_SUCCESS;
}
catch (const std::exception& exc)
{
fprintf(stderr, "\nEXCEPTION in cccl_device_unique_by_key_cleanup(): %s\n", exc.what());
return CUDA_ERROR_UNKNOWN;
}

View File

@@ -0,0 +1,187 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA C++ Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <cstring>
#include <filesystem>
#include <string>
#include <string_view>
#include <vector>
#include <cccl/c/types.h>
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
namespace cccl::detail
{
// Strip a leading "-I" prefix from `s`. Centralizes the prefix-handling used
// by every entry point that accepts an `-I`-prefixed include path from the
// Python layer (which speaks compile-flag syntax) and converts it to a bare
// filesystem path (which hostjit's CompilerConfig wants).
//
// Returns an empty string for null / empty input — every caller treats that
// as "no path supplied" so the bare check stays in one place.
inline std::string strip_dash_i_prefix(const char* s)
{
if (!s || s[0] == '\0')
{
return {};
}
std::string_view sv{s};
if (sv.starts_with("-I"))
{
sv.remove_prefix(2);
}
return std::string{sv};
}
// Parse path arguments from the Python layer for use with hostjit.
// Returns the bare CCCL include path (strips "-I" prefix if present).
inline std::string parse_cccl_include_path(const char* libcudacxx_path)
{
return strip_dash_i_prefix(libcudacxx_path);
}
// Returns the CTK root directory (strips "-I" prefix and "/include" suffix if
// present, then walks up to the directory that contains `nvvm/libdevice/`).
// Works for both the flat `/usr/local/cuda` layout and the
// `/usr/local/cuda/targets/<arch>/include` layout (and any other arrangement)
// because it locates the toolkit root by its `nvvm/libdevice/libdevice.10.bc`
// marker rather than by hard-coded directory structure.
inline std::string parse_ctk_root(const char* ctk_path)
{
std::string p = strip_dash_i_prefix(ctk_path);
if (p.empty())
{
return {};
}
std::filesystem::path fp(p);
if (fp.filename() == "include")
{
fp = fp.parent_path();
}
for (auto candidate = fp; candidate.has_parent_path() && candidate != candidate.parent_path();
candidate = candidate.parent_path())
{
if (std::filesystem::exists(candidate / "nvvm" / "libdevice"))
{
return candidate.string();
}
}
return fp.string();
}
// In source-tree (dev) builds, cub/ and thrust/ live at sibling paths to
// libcudacxx/include rather than under a single CCCL_INCLUDE_PATH. The test
// harness passes them as `-I`-prefixed strings; hostjit's
// `internal-isystem` plumbing only honors a single `cccl_include_path` for
// libcudacxx/cub/thrust, so push the bare cub/thrust paths into
// `include_paths` (`-I <path>`) instead.
inline void
add_extra_cub_thrust_includes(hostjit::CompilerConfig& jit_config, const char* cub_path, const char* thrust_path)
{
auto add_if_dir = [&](const std::string& p) {
if (!p.empty() && std::filesystem::exists(p))
{
jit_config.include_paths.push_back(p);
}
};
add_if_dir(strip_dash_i_prefix(cub_path));
add_if_dir(strip_dash_i_prefix(thrust_path));
}
// RAII helper for merging cub_path / thrust_path (`-I`-prefixed) into a
// `cccl_build_config*`'s `extra_include_dirs` before passing to
// `CubCall::compile()`. The merged config and the strings it points into are
// kept alive for the lifetime of this object.
//
// Usage:
// MergedBuildConfig merged(build_config, cub_path, thrust_path);
// ... .compile(cc_major, cc_minor, merged.get(), ctk_root, ccl_inc);
class MergedBuildConfig
{
public:
MergedBuildConfig(const cccl_build_config* base, const char* cub_path, const char* thrust_path)
{
if (base)
{
merged_ = *base;
}
// We append at most two paths (cub + thrust). Reserve up front so the
// owned_strs_/ptrs_ vectors don't reallocate — important because we
// capture pointers into owned_strs_ for `extra_include_dirs`.
owned_strs_.reserve(2);
ptrs_.reserve(merged_.num_extra_include_dirs + 2);
for (size_t i = 0; i < merged_.num_extra_include_dirs; ++i)
{
ptrs_.push_back(merged_.extra_include_dirs[i]);
}
auto add = [&](const char* p) {
auto s = strip_dash_i_prefix(p);
if (!s.empty())
{
owned_strs_.push_back(std::move(s));
}
};
add(cub_path);
add(thrust_path);
for (auto& s : owned_strs_)
{
ptrs_.push_back(s.c_str());
}
merged_.extra_include_dirs = ptrs_.data();
merged_.num_extra_include_dirs = ptrs_.size();
}
cccl_build_config* get()
{
return &merged_;
}
private:
cccl_build_config merged_{};
std::vector<std::string> owned_strs_;
std::vector<const char*> ptrs_;
};
// Copy cubin data into a heap-allocated buffer the caller owns. Plain `new[]`
// — memcpy is noexcept so there's no exception path between the allocation
// and the assignment to out_cubin. The caller eventually frees via
// release_jit_artifacts() (or delete[] on out_cubin).
inline void copy_cubin(const std::vector<char>& cubin, void*& out_cubin, size_t& out_size)
{
if (cubin.empty())
{
out_cubin = nullptr;
out_size = 0;
return;
}
auto* buf = new char[cubin.size()];
std::memcpy(buf, cubin.data(), cubin.size());
out_cubin = buf;
out_size = cubin.size();
}
// Free the JIT compiler and cubin buffer common to every build_result_t in
// c/parallel.v2/. Algorithm-specific fields (X_fn, determinism, etc.) get
// nulled by the caller after this. Template'd over the build_result type so
// each algorithm header doesn't need to include this one transitively.
template <typename BuildResult>
void release_jit_artifacts(BuildResult* build_ptr)
{
delete static_cast<hostjit::JITCompiler*>(build_ptr->jit_compiler);
build_ptr->jit_compiler = nullptr;
delete[] static_cast<char*>(build_ptr->payload);
build_ptr->payload = nullptr;
build_ptr->payload_size = 0;
}
} // namespace cccl::detail

View File

@@ -0,0 +1,53 @@
//===----------------------------------------------------------------------===//
//
// Part of CUDA Experimental in CUDA Core Compute 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
#pragma once
#include <atomic>
#include <mutex>
namespace cccl::detail
{
// Serializes the first successful invocation of generated CUB code that
// lazily initializes a function-local static. Windows HostJIT translation
// units use -fno-threadsafe-statics because the required CRT support is not
// available. Callers bypass the gate for empty work; after initialization,
// the atomic fast path invokes the generated function without locking.
class first_call_gate
{
public:
template <typename Invocation>
int invoke(Invocation&& invocation)
{
if (complete.load(std::memory_order_acquire))
{
return invocation();
}
std::unique_lock lock(mutex);
if (complete.load(std::memory_order_relaxed))
{
lock.unlock();
return invocation();
}
const int status = invocation();
if (status == 0)
{
complete.store(true, std::memory_order_release);
}
return status;
}
private:
std::atomic<bool> complete{false};
std::mutex mutex;
};
} // namespace cccl::detail

View File

@@ -0,0 +1,69 @@
cccl_get_c2h()
# v2 reuses the same test sources as v1 from c/parallel/test/, compiled with
# CCCL_C_PARALLEL_V2 defined. The shared source files contain a small number
# of #ifdef CCCL_C_PARALLEL_V2 branches that select v2's backend (hostjit
# bitcode vs nvrtc LTO-IR) and skip v1-only test variants.
set(v1_test_dir "${CMAKE_CURRENT_SOURCE_DIR}/../../parallel/test")
function(cccl_c_parallel_v2_add_test target_name_var source)
get_filename_component(target_name "${source}" NAME_WE)
string(
REGEX REPLACE
"test_([^.]*)"
"cccl.c.parallel.v2.test.\\1"
target_name
"${target_name}"
)
set(${target_name_var} ${target_name} PARENT_SCOPE)
add_executable(${target_name} "${source}")
cccl_configure_target(${target_name} DIALECT 20)
set_target_properties(${target_name} PROPERTIES CUDA_RUNTIME_LIBRARY STATIC)
target_link_libraries(
${target_name}
PRIVATE
cccl.compiler_interface
cccl.c.parallel.v2
cccl.c.parallel.v2.hostjit_lib
CUDA::cudart_static
CUDA::nvrtc
cccl.c2h.main
)
target_include_directories(
${target_name}
PRIVATE
"${v1_test_dir}"
"${CMAKE_CURRENT_SOURCE_DIR}/../src/hostjit/include"
)
list(GET CUDAToolkit_INCLUDE_DIRS 0 CUDA_FIRST_INCLUDE_DIR)
target_compile_definitions(
${target_name}
PRIVATE
CCCL_C_PARALLEL_V2=1
TEST_CUB_PATH="-I${CCCL_SOURCE_DIR}/cub"
TEST_THRUST_PATH="-I${CCCL_SOURCE_DIR}/thrust"
TEST_LIBCUDACXX_PATH="-I${CCCL_SOURCE_DIR}/libcudacxx/include"
TEST_CTK_PATH="-I${CUDA_FIRST_INCLUDE_DIR}"
TEST_INCLUDE_PATH="${v1_test_dir}"
)
add_test(NAME ${target_name} COMMAND ${target_name})
endfunction()
file(
GLOB test_srcs
RELATIVE "${v1_test_dir}"
CONFIGURE_DEPENDS
"${v1_test_dir}/*.cu"
"${v1_test_dir}/*.cpp"
)
foreach (test_src IN LISTS test_srcs)
cccl_c_parallel_v2_add_test(test_target "${v1_test_dir}/${test_src}")
endforeach()
add_subdirectory(freestanding)

View File

@@ -0,0 +1,36 @@
cccl_get_c2h()
function(cccl_c_parallel_v2_add_freestanding_test target_name_var source)
get_filename_component(target_name "${source}" NAME_WE)
string(
REGEX REPLACE
"test_([^.]*)"
"cccl.c.parallel.v2.test.freestanding.\\1"
target_name
"${target_name}"
)
set(target_name_var ${target_name} PARENT_SCOPE)
cccl_add_executable(
${target_name}
ADD_CTEST
NO_METATARGETS
DIALECT 20
SOURCES "${source}"
)
target_link_libraries(
${target_name}
PRIVATE cccl.c.parallel.v2.hostjit_lib CUDA::cudart
)
endfunction()
file(
GLOB freestanding_srcs
RELATIVE "${CMAKE_CURRENT_LIST_DIR}"
CONFIGURE_DEPENDS
*.cpp
)
foreach (freestanding_src IN LISTS freestanding_srcs)
cccl_c_parallel_v2_add_freestanding_test(test_target "${freestanding_src}")
endforeach()

View File

@@ -0,0 +1,69 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cassert>
#include <cstdio>
#include <cuda_runtime.h>
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
static const char* k_source = R"(
#include <cuda/std/initializer_list>
#include <cuda_runtime.h>
__global__ void device_kernel(int* ptr)
{
::cuda::std::initializer_list<::std::size_t> meow {42ull, 1337ull};
*ptr = static_cast<int>(::std::move(*meow.begin()));
}
extern "C" _CCCL_VISIBILITY_EXPORT void host_entry(int* ptr)
{
device_kernel<<<1, 1>>>(ptr);
}
)";
int main()
{
// Detect Clang/CUDA configuration from the build environment
auto config = hostjit::detectDefaultConfig();
hostjit::JITCompiler compiler(config);
if (!compiler.compile(k_source))
{
std::fprintf(stderr, "HostJIT compilation failed:\n%s\n", compiler.getLastError().c_str());
return 1;
}
auto host_fn = compiler.getFunction<void (*)(int*)>("host_entry");
if (!host_fn)
{
std::fprintf(stderr, "Symbol 'host_entry' not found\n");
return 1;
}
int* d_ptr = nullptr;
cudaMalloc(&d_ptr, sizeof(int));
host_fn(d_ptr);
cudaDeviceSynchronize();
int result = 0;
cudaMemcpy(&result, d_ptr, sizeof(int), cudaMemcpyDeviceToHost);
cudaFree(d_ptr);
assert(result == 42 && "device kernel did not write expected value");
std::printf("freestanding compiler test passed (result=%d)\n", result);
return 0;
}

View File

@@ -0,0 +1,66 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cassert>
#include <cstdio>
#include <cuda_runtime.h>
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
static const char* k_source = R"(
#include <cuda_runtime.h>
#include <cuda/std/version>
__global__ void device_kernel(int* ptr)
{
*ptr = 42;
}
extern "C" _CCCL_VISIBILITY_EXPORT void host_entry(int* ptr)
{
device_kernel<<<1, 1>>>(ptr);
}
)";
int main()
{
// Detect Clang/CUDA configuration from the build environment
auto config = hostjit::detectDefaultConfig();
hostjit::JITCompiler compiler(config);
if (!compiler.compile(k_source))
{
std::fprintf(stderr, "HostJIT compilation failed:\n%s\n", compiler.getLastError().c_str());
return 1;
}
auto host_fn = compiler.getFunction<void (*)(int*)>("host_entry");
if (!host_fn)
{
std::fprintf(stderr, "Symbol 'host_entry' not found\n");
return 1;
}
int* d_ptr = nullptr;
cudaMalloc(&d_ptr, sizeof(int));
host_fn(d_ptr);
cudaDeviceSynchronize();
int result = 0;
cudaMemcpy(&result, d_ptr, sizeof(int), cudaMemcpyDeviceToHost);
cudaFree(d_ptr);
assert(result == 42 && "device kernel did not write expected value");
std::printf("freestanding compiler test passed (result=%d)\n", result);
return 0;
}

View File

@@ -0,0 +1,131 @@
#include <iostream>
#include <numeric>
#include <cuda_runtime.h>
#include "test_util.h"
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
static const char* cuda_source = R"(
#include <cuda_runtime.h>
#include <cub/device/device_adjacent_difference.cuh>
#include <cuda/std/functional>
extern "C" _CCCL_VISIBILITY_EXPORT int adjacentDifference(
const int* d_input, int* d_output, int num_items) {
void* d_temp_storage = nullptr;
size_t temp_storage_bytes = 0;
// Query temp storage
cudaError_t err = cub::DeviceAdjacentDifference::SubtractLeftCopy(
d_temp_storage, temp_storage_bytes, d_input, d_output, num_items,
cuda::std::minus<int>{});
if (err != cudaSuccess) return -1;
// Allocate temp storage
err = cudaMalloc(&d_temp_storage, temp_storage_bytes);
if (err != cudaSuccess) return -2;
// Run adjacent difference
err = cub::DeviceAdjacentDifference::SubtractLeftCopy(
d_temp_storage, temp_storage_bytes, d_input, d_output, num_items,
cuda::std::minus<int>{});
if (err != cudaSuccess) {
cudaFree(d_temp_storage);
return -3;
}
err = cudaDeviceSynchronize();
cudaFree(d_temp_storage);
return (err == cudaSuccess) ? 0 : -4;
}
)";
int main()
{
// Detect Clang/CUDA configuration from the build environment
auto config = hostjit::detectDefaultConfig();
hostjit::JITCompiler compiler(config);
if (!compiler.compile(cuda_source))
{
std::fprintf(stderr, "HostJIT compilation failed:\n%s\n", compiler.getLastError().c_str());
return 1;
}
auto adjacentDiff = compiler.getFunction<int (*)(const int*, int*, int)>("adjacentDifference");
if (!adjacentDiff)
{
std::cerr << "Failed to get function: " << compiler.getLastError() << "\n";
return 1;
}
// Prepare test data: [1, 2, 3, 4, 5, ...]
const int N = 8;
std::vector<int> h_input(N);
std::iota(h_input.begin(), h_input.end(), 1);
// Expected: SubtractLeft produces [d[0], d[1]-d[0], d[2]-d[1], ...]
// For input [1,2,3,4,5,6,7,8]: output [1,1,1,1,1,1,1,1]
std::vector<int> expected(N);
expected[0] = h_input[0];
for (int i = 1; i < N; i++)
{
expected[i] = h_input[i] - h_input[i - 1];
}
int *d_input, *d_output;
CUDA_CHECK(cudaMalloc(&d_input, N * sizeof(int)));
CUDA_CHECK(cudaMalloc(&d_output, N * sizeof(int)));
CUDA_CHECK(cudaMemcpy(d_input, h_input.data(), N * sizeof(int), cudaMemcpyHostToDevice));
std::cout << "Computing adjacent differences of " << N << " integers...\n";
int status = adjacentDiff(d_input, d_output, N);
if (status != 0)
{
std::cerr << "adjacentDifference failed with status: " << status << "\n";
CUDA_CHECK(cudaFree(d_input));
CUDA_CHECK(cudaFree(d_output));
return 1;
}
std::vector<int> h_output(N);
CUDA_CHECK(cudaMemcpy(h_output.data(), d_output, N * sizeof(int), cudaMemcpyDeviceToHost));
std::cout << "Input: [";
for (int i = 0; i < N; i++)
{
std::cout << (i ? ", " : "") << h_input[i];
}
std::cout << "]\n";
std::cout << "Output: [";
for (int i = 0; i < N; i++)
{
std::cout << (i ? ", " : "") << h_output[i];
}
std::cout << "]\n";
std::cout << "Expected: [";
for (int i = 0; i < N; i++)
{
std::cout << (i ? ", " : "") << expected[i];
}
std::cout << "]\n";
bool success = (h_output == expected);
if (success)
{
std::cout << "Results verified successfully!\n";
}
else
{
std::cerr << "Mismatch!\n";
}
CUDA_CHECK(cudaFree(d_input));
CUDA_CHECK(cudaFree(d_output));
return success ? 0 : 1;
}

View File

@@ -0,0 +1,169 @@
#include <filesystem>
#include <fstream>
#include <iostream>
#include <numeric>
#include <cuda_runtime.h>
#include "test_util.h"
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
// LLVM IR for an add operator with alwaysinline attribute
// This IR defines a function: float user_op(float a, float b) { return a + b; }
const char* user_op_llvm_ir = R"(
target datalayout = "e-p6:32:32-i64:64-i128:128-i256:256-v16:16-v32:32-n16:32:64"
target triple = "nvptx64-nvidia-cuda"
define float @user_op(float %a, float %b) alwaysinline {
entry:
%result = fadd float %a, %b
ret float %result
}
)";
// Write LLVM IR text to a file (parseIRFile in the compiler handles both .ll and .bc)
bool writeIRFile(const std::string& llvm_ir, const std::string& output_path)
{
std::ofstream ir_file(output_path);
if (!ir_file)
{
std::cerr << "Failed to write LLVM IR file\n";
return false;
}
ir_file << llvm_ir;
ir_file.close();
std::cout << "Generated IR file: " << output_path << "\n";
return true;
}
static const char* cuda_source = R"(
#include <cuda_runtime.h>
#include <cub/device/device_reduce.cuh>
// External declaration - resolved from linked bitcode
extern "C" __device__ float user_op(float a, float b);
// Functor wrapping the external function
struct UserOp {
__device__ __forceinline__
float operator()(float a, float b) const {
return user_op(a, b);
}
};
extern "C" _CCCL_VISIBILITY_EXPORT int computeReduce(float* d_input, int num_items, float* result) {
float* d_output = nullptr;
void* d_temp_storage = nullptr;
size_t temp_storage_bytes = 0;
float init = HOSTJIT_REDUCE_INIT;
UserOp op;
cudaError_t err = cudaMalloc(&d_output, sizeof(float));
if (err != cudaSuccess) return -1;
err = cub::DeviceReduce::Reduce(d_temp_storage, temp_storage_bytes,
d_input, d_output, num_items, op, init);
if (err != cudaSuccess) {
cudaFree(d_output);
return -2;
}
err = cudaMalloc(&d_temp_storage, temp_storage_bytes);
if (err != cudaSuccess) {
cudaFree(d_output);
return -3;
}
err = cub::DeviceReduce::Reduce(d_temp_storage, temp_storage_bytes,
d_input, d_output, num_items, op, init);
if (err != cudaSuccess) {
cudaFree(d_temp_storage);
cudaFree(d_output);
return -4;
}
err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
cudaFree(d_temp_storage);
cudaFree(d_output);
return -5;
}
err = cudaMemcpy(result, d_output, sizeof(float), cudaMemcpyDeviceToHost);
cudaFree(d_temp_storage);
cudaFree(d_output);
return (err == cudaSuccess) ? 0 : -6;
}
)";
int main()
{
// Step 1: Write LLVM IR file with the custom operator
std::string ir_path = (std::filesystem::temp_directory_path() / "user_op.ll").string();
if (!writeIRFile(user_op_llvm_ir, ir_path))
{
std::cerr << "Failed to write IR file\n";
return 1;
}
auto config = hostjit::detectDefaultConfig();
config.device_bitcode_files.push_back(ir_path);
config.macro_definitions["HOSTJIT_REDUCE_INIT"] = "0.0f";
hostjit::JITCompiler compiler(config);
if (!compiler.compile(cuda_source))
{
std::cerr << "Failed to get function: " << compiler.getLastError() << "\n";
return 1;
}
auto computeReduce = compiler.getFunction<int (*)(float*, int, float*)>("computeReduce");
if (!computeReduce)
{
std::cerr << "Failed to get function: " << compiler.getLastError() << "\n";
return 1;
}
// Prepare test data
const int N = 1024;
std::vector<float> h_input(N);
// Initialize with values 1.0 to N
std::iota(h_input.begin(), h_input.end(), 1.0f);
const float expected = static_cast<float>(N) * (N + 1) / 2.0f; // Sum of 1..N
// Allocate device memory
float* d_input;
CUDA_CHECK(cudaMalloc(&d_input, N * sizeof(float)));
// Copy data to device
CUDA_CHECK(cudaMemcpy(d_input, h_input.data(), N * sizeof(float), cudaMemcpyHostToDevice));
float result = -1.0f;
int status = computeReduce(d_input, N, &result);
if (status != 0)
{
std::cerr << "computeReduce failed with status: " << status << "\n";
CUDA_CHECK(cudaFree(d_input));
return 1;
}
// Verify result
std::cout << "\nResult: " << result << " (expected: " << expected << ")\n";
bool success = std::abs(result - expected) < 0.01f;
if (success)
{
std::cout << "\n*** SUCCESS: Custom operator was inlined and executed correctly! ***\n";
}
else
{
std::cerr << "\nMismatch! Got " << result << " but expected " << expected << "\n";
}
// Cleanup
CUDA_CHECK(cudaFree(d_input));
return success ? 0 : 1;
}

View File

@@ -0,0 +1,131 @@
#include <iostream>
#include <numeric>
#include <cuda_runtime.h>
#include "test_util.h"
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
static const char* cuda_source = R"(
#include <cuda_runtime.h>
#include <cub/device/device_reduce.cuh>
// External declaration - resolved from linked bitcode
extern "C" __device__ float user_op(float a, float b);
// Functor wrapping the external function
struct UserOp {
__device__ __forceinline__
float operator()(float a, float b) const {
return ::cuda::std::min(a, b);
}
};
extern "C" _CCCL_VISIBILITY_EXPORT int computeReduce(float* d_input, int num_items, float* result) {
float* d_output = nullptr;
void* d_temp_storage = nullptr;
size_t temp_storage_bytes = 0;
float init = 1e38f;
UserOp op;
cudaError_t err = cudaMalloc(&d_output, sizeof(float));
if (err != cudaSuccess) return -1;
err = cub::DeviceReduce::Reduce(d_temp_storage, temp_storage_bytes,
d_input, d_output, num_items, op, init);
if (err != cudaSuccess) {
cudaFree(d_output);
return -2;
}
err = cudaMalloc(&d_temp_storage, temp_storage_bytes);
if (err != cudaSuccess) {
cudaFree(d_output);
return -3;
}
err = cub::DeviceReduce::Reduce(d_temp_storage, temp_storage_bytes,
d_input, d_output, num_items, op, init);
if (err != cudaSuccess) {
cudaFree(d_temp_storage);
cudaFree(d_output);
return -4;
}
err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
cudaFree(d_temp_storage);
cudaFree(d_output);
return -5;
}
err = cudaMemcpy(result, d_output, sizeof(float), cudaMemcpyDeviceToHost);
cudaFree(d_temp_storage);
cudaFree(d_output);
return (err == cudaSuccess) ? 0 : -6;
}
)";
int main()
{
// Detect Clang/CUDA configuration from the build environment
auto config = hostjit::detectDefaultConfig();
hostjit::JITCompiler compiler(config);
if (!compiler.compile(cuda_source))
{
std::cerr << "Failed to get function: " << compiler.getLastError() << "\n";
return 1;
}
auto computeReduce = compiler.getFunction<int (*)(float*, int, float*)>("computeReduce");
if (!computeReduce)
{
std::cerr << "Failed to get function: " << compiler.getLastError() << "\n";
return 1;
}
// Prepare test data
const int N = 1024;
std::vector<float> h_input(N);
// Initialize with values 1.0 to N
std::iota(h_input.begin(), h_input.end(), 1.0f);
const float expected = 1.0f; // Minimum value in the array
// Allocate device memory
float* d_input;
CUDA_CHECK(cudaMalloc(&d_input, N * sizeof(float)));
// Copy data to device
CUDA_CHECK(cudaMemcpy(d_input, h_input.data(), N * sizeof(float), cudaMemcpyHostToDevice));
float result = -1.0f;
int status = computeReduce(d_input, N, &result);
if (status != 0)
{
std::cerr << "computeReduce failed with status: " << status << "\n";
CUDA_CHECK(cudaFree(d_input));
return 1;
}
// Verify result
std::cout << "\nResult: " << result << " (expected: " << expected << ")\n";
bool success = std::abs(result - expected) < 0.01f;
if (success)
{
std::cout << "\n*** SUCCESS: Custom operator was inlined and executed correctly! ***\n";
}
else
{
std::cerr << "\nMismatch! Got " << result << " but expected " << expected << "\n";
}
// Cleanup
CUDA_CHECK(cudaFree(d_input));
return success ? 0 : 1;
}

View File

@@ -0,0 +1,145 @@
#include <iostream>
#include <numeric>
#include <cuda_runtime.h>
#include "test_util.h"
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
static const char* cuda_source = R"(
#include <cuda_runtime.h>
#include <cub/device/device_reduce.cuh>
#include <cuda/std/functional>
#include <cuda/__execution/determinism.h>
#include <cuda/__execution/require.h>
extern "C" _CCCL_VISIBILITY_EXPORT int computeSumDeterministic(float* d_input, int num_items, float* result) {
float* d_output = nullptr;
// Allocate output
cudaError_t err = cudaMalloc(&d_output, sizeof(float));
if (err != cudaSuccess) return -1;
// Run deterministic sum reduction with gpu_to_gpu determinism
auto env = cuda::execution::require(cuda::execution::determinism::gpu_to_gpu);
err = cub::DeviceReduce::Sum(d_input, d_output, num_items, env);
if (err != cudaSuccess) {
cudaFree(d_output);
return -2;
}
// Synchronize
err = cudaDeviceSynchronize();
if (err != cudaSuccess) {
cudaFree(d_output);
return -3;
}
// Copy result back
err = cudaMemcpy(result, d_output, sizeof(float), cudaMemcpyDeviceToHost);
if (err != cudaSuccess) {
cudaFree(d_output);
return -4;
}
// Cleanup
cudaFree(d_output);
return 0;
}
)";
int main()
{
// Detect Clang/CUDA configuration from the build environment
auto config = hostjit::detectDefaultConfig();
hostjit::JITCompiler compiler(config);
if (!compiler.compile(cuda_source))
{
std::fprintf(stderr, "HostJIT compilation failed:\n%s\n", compiler.getLastError().c_str());
return 1;
}
auto computeSumDeterministic = compiler.getFunction<int (*)(float*, int, float*)>("computeSumDeterministic");
if (!computeSumDeterministic)
{
std::cerr << "Failed to get function: " << compiler.getLastError() << "\n";
return 1;
}
// Prepare test data
const int N = 1024;
std::vector<float> h_input(N);
// Initialize with values 1.0 to N
std::iota(h_input.begin(), h_input.end(), 1.0f);
// Expected sum: N*(N+1)/2
float expected_sum = static_cast<float>(N) * (N + 1) / 2;
// Allocate device memory
float* d_input;
CUDA_CHECK(cudaMalloc(&d_input, N * sizeof(float)));
// Copy data to device
CUDA_CHECK(cudaMemcpy(d_input, h_input.data(), N * sizeof(float), cudaMemcpyHostToDevice));
// Call the JIT-compiled function
std::cout << "Computing deterministic sum of " << N << " floats using CUB...\n";
float result = 0;
int status = computeSumDeterministic(d_input, N, &result);
if (status != 0)
{
std::cerr << "computeSumDeterministic failed with status: " << status << "\n";
CUDA_CHECK(cudaFree(d_input));
return 1;
}
// Verify result
std::cout << "Result: " << result << " (expected: " << expected_sum << ")\n";
// Verify determinism: run multiple times and check results are identical
const int num_runs = 10;
bool deterministic = true;
for (int i = 0; i < num_runs; ++i)
{
float run_result = 0;
status = computeSumDeterministic(d_input, N, &run_result);
if (status != 0)
{
std::cerr << "Run " << i << " failed with status: " << status << "\n";
CUDA_CHECK(cudaFree(d_input));
return 1;
}
if (run_result != result)
{
std::cerr << "Run " << i << " produced different result: " << run_result << " vs " << result << "\n";
deterministic = false;
}
}
bool success = (result == expected_sum) && deterministic;
if (success)
{
std::cout << "Results verified successfully! (" << num_runs << " runs produced identical results)\n";
}
else
{
if (result != expected_sum)
{
std::cerr << "Mismatch! Got " << result << " but expected " << expected_sum << "\n";
}
if (!deterministic)
{
std::cerr << "Non-deterministic results detected!\n";
}
}
// Cleanup
CUDA_CHECK(cudaFree(d_input));
return success ? 0 : 1;
}

View File

@@ -0,0 +1,108 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION.
//
//===----------------------------------------------------------------------===//
// Repro harness: feeds a CUDA source string to v1's hostjit and reports
// whether compilation succeeds. If a path is passed via argv[1] or
// $REPRO_SOURCE_FILE, that file's contents are compiled instead of the
// built-in minimal source. Used to test whether v2's actual CubCall-generated
// host_input.cu compiles under v1's hostjit infrastructure.
#include <cstdlib>
#include <fstream>
#include <iostream>
#include <sstream>
#include <string>
#include <cuda_runtime.h>
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
static const char* default_source = R"(
#include <cuda_runtime.h>
#include <cub/device/device_reduce.cuh>
using in_0_it_t = int*;
using out_0_it_t = unsigned long long*;
struct Op_0 {
__device__ __forceinline__
unsigned long long operator()(unsigned long long a, unsigned long long b) const {
return a + b;
}
};
extern "C" __attribute__((visibility("default"))) int cccl_jit_reduce(
void* d_temp_storage,
size_t* temp_storage_bytes,
void* d_in_state,
void* d_out_state,
unsigned long long num_items,
void* /*op_state*/,
void* init_state)
{
in_0_it_t d_in = static_cast<in_0_it_t>(d_in_state);
out_0_it_t d_out = static_cast<out_0_it_t>(d_out_state);
unsigned long long init = *static_cast<unsigned long long*>(init_state);
Op_0 op;
cudaError_t err = cub::DeviceReduce::Reduce<in_0_it_t, out_0_it_t, Op_0, int, unsigned long long>(
d_temp_storage, *temp_storage_bytes, d_in, d_out,
static_cast<int>(num_items), op, init);
return err == cudaSuccess ? 0 : -1;
}
)";
int main(int argc, char** argv)
{
std::string source_str;
std::string source_path;
if (argc > 1)
{
source_path = argv[1];
}
else if (const char* env = std::getenv("REPRO_SOURCE_FILE"))
{
source_path = env;
}
if (!source_path.empty())
{
std::ifstream f(source_path);
if (!f)
{
std::cerr << "Failed to open: " << source_path << std::endl;
return 2;
}
std::stringstream ss;
ss << f.rdbuf();
source_str = ss.str();
std::cerr << "Loaded " << source_str.size() << " bytes from " << source_path << std::endl;
}
else
{
source_str = default_source;
std::cerr << "Using built-in default source." << std::endl;
}
hostjit::CompilerConfig config = hostjit::detectDefaultConfig();
config.sm_version = 80;
config.verbose = false;
hostjit::JITCompiler compiler(config);
if (!compiler.compile(source_str))
{
std::cerr << "JIT compilation FAILED:\n" << compiler.getLastError() << std::endl;
return 1;
}
std::cout << "JIT compilation succeeded." << std::endl;
return 0;
}

View File

@@ -0,0 +1,71 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#include <cassert>
#include <cstdio>
#include <cuda_runtime.h>
#include <hostjit/config.hpp>
#include <hostjit/jit_compiler.hpp>
static const char* k_source = R"(
#include <initializer_list>
#include <utility>
#include <cuda/std/version>
#include <cuda_runtime.h>
__global__ void device_kernel(int* ptr)
{
::std::initializer_list<::std::size_t> meow {42ull, 1337ull};
*ptr = static_cast<int>(::std::move(*meow.begin()));
}
extern "C" _CCCL_VISIBILITY_EXPORT void host_entry(int* ptr)
{
device_kernel<<<1, 1>>>(ptr);
}
)";
int main()
{
// Detect Clang/CUDA configuration from the build environment
auto config = hostjit::detectDefaultConfig();
hostjit::JITCompiler compiler(config);
if (!compiler.compile(k_source))
{
std::fprintf(stderr, "HostJIT compilation failed:\n%s\n", compiler.getLastError().c_str());
return 1;
}
auto host_fn = compiler.getFunction<void (*)(int*)>("host_entry");
if (!host_fn)
{
std::fprintf(stderr, "Symbol 'host_entry' not found\n");
return 1;
}
int* d_ptr = nullptr;
cudaMalloc(&d_ptr, sizeof(int));
host_fn(d_ptr);
cudaDeviceSynchronize();
int result = 0;
cudaMemcpy(&result, d_ptr, sizeof(int), cudaMemcpyDeviceToHost);
cudaFree(d_ptr);
assert(result == 42 && "device kernel did not write expected value");
std::printf("freestanding compiler test passed (result=%d)\n", result);
return 0;
}

View File

@@ -0,0 +1,30 @@
//===----------------------------------------------------------------------===//
//
// 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) 2026 NVIDIA CORPORATION & AFFILIATES.
//
//===----------------------------------------------------------------------===//
#ifndef C_PARALLEL_FREESTANDING_TEST_UTIL_H
#define C_PARALLEL_FREESTANDING_TEST_UTIL_H
#include <cassert>
#include <iostream>
#include <cuda_runtime.h>
#define CUDA_CHECK(call) \
do \
{ \
cudaError_t err = call; \
if (err != cudaSuccess) \
{ \
std::cerr << "CUDA error at " << __FILE__ << ":" << __LINE__ << ": " << cudaGetErrorString(err) << "\n"; \
return 1; \
} \
} while (0)
#endif // C_PARALLEL_FREESTANDING_TEST_UTIL_H