[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:
283
cccl_upstream/c/parallel.v2/src/hostjit/CMakeLists.txt
Normal file
283
cccl_upstream/c/parallel.v2/src/hostjit/CMakeLists.txt
Normal 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()
|
||||
237
cccl_upstream/c/parallel.v2/src/hostjit/codegen/bitcode.cpp
Normal file
237
cccl_upstream/c/parallel.v2/src/hostjit/codegen/bitcode.cpp
Normal 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
|
||||
812
cccl_upstream/c/parallel.v2/src/hostjit/codegen/cub_call.cpp
Normal file
812
cccl_upstream/c/parallel.v2/src/hostjit/codegen/cub_call.cpp
Normal 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
|
||||
288
cccl_upstream/c/parallel.v2/src/hostjit/codegen/iterators.cpp
Normal file
288
cccl_upstream/c/parallel.v2/src/hostjit/codegen/iterators.cpp
Normal 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
|
||||
613
cccl_upstream/c/parallel.v2/src/hostjit/codegen/operators.cpp
Normal file
613
cccl_upstream/c/parallel.v2/src/hostjit/codegen/operators.cpp
Normal 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
|
||||
73
cccl_upstream/c/parallel.v2/src/hostjit/codegen/types.cpp
Normal file
73
cccl_upstream/c/parallel.v2/src/hostjit/codegen/types.cpp
Normal 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
|
||||
1727
cccl_upstream/c/parallel.v2/src/hostjit/compiler.cpp
Normal file
1727
cccl_upstream/c/parallel.v2/src/hostjit/compiler.cpp
Normal file
File diff suppressed because it is too large
Load Diff
175
cccl_upstream/c/parallel.v2/src/hostjit/config.cpp
Normal file
175
cccl_upstream/c/parallel.v2/src/hostjit/config.cpp
Normal 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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
File diff suppressed because it is too large
Load Diff
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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__
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,7 @@
|
||||
// Minimal climits stub for CUDA JIT compilation
|
||||
#ifndef _HOSTJIT_CLIMITS
|
||||
#define _HOSTJIT_CLIMITS
|
||||
|
||||
#include <limits.h>
|
||||
|
||||
#endif // _HOSTJIT_CLIMITS
|
||||
@@ -0,0 +1,7 @@
|
||||
// Minimal cmath stub for CUDA JIT compilation
|
||||
#ifndef _HOSTJIT_CMATH
|
||||
#define _HOSTJIT_CMATH
|
||||
|
||||
#include <math.h>
|
||||
|
||||
#endif // _HOSTJIT_CMATH
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,3 @@
|
||||
#ifndef _HOSTJIT_CTYPE_H
|
||||
#define _HOSTJIT_CTYPE_H
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -0,0 +1,4 @@
|
||||
#ifndef _HOSTJIT_MEMORY_H
|
||||
#define _HOSTJIT_MEMORY_H
|
||||
#include <string.h>
|
||||
#endif
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
192
cccl_upstream/c/parallel.v2/src/hostjit/jit_compiler.cpp
Normal file
192
cccl_upstream/c/parallel.v2/src/hostjit/jit_compiler.cpp
Normal 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
|
||||
210
cccl_upstream/c/parallel.v2/src/hostjit/loader.cpp
Normal file
210
cccl_upstream/c/parallel.v2/src/hostjit/loader.cpp
Normal 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
|
||||
Reference in New Issue
Block a user