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