9#include <cuda_runtime.h>
10#include <cuda/std/array>
11#include <cuda/std/span>
20#include <initializer_list>
29__host__ __device__
inline int4
Xor(int4 lhs, int4 rhs) {
30 return {lhs.x ^ rhs.x, lhs.y ^ rhs.y, lhs.z ^ rhs.z, lhs.w ^ rhs.w};
42 requires(N != cuda::std::dynamic_extent)
43__host__ __device__
inline cuda::std::array<int4, N>
Xor(
44 cuda::std::span<const int4, N> lhs, cuda::std::span<const int4, N> rhs) {
45 cuda::std::array<int4, N> out{};
46 for (
size_t i = 0; i < N; ++i) out[i] =
Xor(lhs[i], rhs[i]);
51__host__ __device__
inline int4
SetLsb(int4 val,
bool bit) {
58__host__ __device__
inline bool GetLsb(int4 val) {
59 return (val.w & 1) != 0;
73__host__ __device__
inline void RequireArg(
bool ok,
const char *msg) {
81namespace fss::detail {
84template <
int in_bits,
typename In>
85__host__
inline bool RangeOutputCount(In first, In last,
size_t capacity,
size_t &count) {
86 if (first > last)
return false;
87 if constexpr (in_bits <
sizeof(In) * CHAR_BIT) {
88 if ((last >> in_bits) != 0)
return false;
90 In difference = last - first;
91 constexpr size_t kMaxCount = std::numeric_limits<size_t>::max() /
sizeof(int4);
92 if (difference >= kMaxCount)
return false;
93 count =
static_cast<size_t>(difference) + 1;
94 return count <= capacity;
104template <
int in_bits,
int frontier_depth,
int threads_per_block,
int frontier_cap = 0,
105 typename In,
typename LaunchChunk>
106__host__
inline cudaError_t SplitRangeChunks(In first, In last,
size_t ys_count,
107 LaunchChunk &&launch) {
108 static_assert(frontier_depth >= 0,
"gpu frontier depth must be nonnegative");
109 static_assert(threads_per_block >= 1 && threads_per_block <= 1024 &&
110 (threads_per_block & (threads_per_block - 1)) == 0,
111 "gpu block size must be a power of two in [1,1024]");
113 if (!RangeOutputCount<in_bits>(first, last, ys_count, count))
return cudaErrorInvalidValue;
114 constexpr int kThreadBits = [] {
116 for (
int threads = threads_per_block; threads > 1; threads >>= 1) ++bits;
119 constexpr int kMaxDepth = [] {
120 int depth = 30 + kThreadBits + frontier_cap;
121 if (depth >=
sizeof(
size_t) * CHAR_BIT) depth =
sizeof(size_t) * CHAR_BIT - 1;
122 if (frontier_depth < depth && depth - frontier_depth > 8) depth = frontier_depth + 8;
125 return ForEachAlignedRange<in_bits>(first, last, kMaxDepth,
126 [&](In chunk_first,
int depth,
size_t offset,
size_t) {
127 int frontier = frontier_depth < depth - frontier_cap ? frontier_depth : depth - frontier_cap;
128 if (frontier < 0) frontier = 0;
129 int thread_bits = kThreadBits < frontier ? kThreadBits : frontier;
130 int root = frontier - thread_bits;
131 size_t blocks =
size_t{1} << root;
132 if (blocks >
static_cast<size_t>(std::numeric_limits<int>::max()))
133 return cudaErrorInvalidConfiguration;
134 return launch(chunk_first, depth, offset, frontier, thread_bits, root, blocks);
140template <
int in_bits,
typename In,
typename Callback>
141__host__
inline cudaError_t ForEachAlignedRange(
142 In first, In last,
int max_depth, Callback callback) {
146 while (depth < max_depth && depth < in_bits &&
147 depth <
sizeof(In) * CHAR_BIT) {
148 In half_mask = (In{1} << depth) - 1;
149 In mask = half_mask * 2 + 1;
150 if ((first & mask) != 0 || last - first < mask)
break;
153 size_t count =
size_t{1} << depth;
154 cudaError_t error = callback(first, depth, offset, count);
155 if (error != cudaSuccess)
return error;
156 if (last - first ==
static_cast<In
>(count - 1))
return cudaSuccess;
157 first +=
static_cast<In
>(count);
163inline int ResolveParDepth(
int par_depth,
int useful_depth =
sizeof(
size_t) * CHAR_BIT - 1) {
164 assert(par_depth >= 0);
165 return par_depth < useful_depth ? par_depth : useful_depth;
168inline size_t ResolveParPartitions(
int par_depth,
size_t count) {
169 assert(par_depth >= 0);
170 if (count == 0)
return 0;
171 int depth = ResolveParDepth(par_depth);
172 size_t partitions = 1;
173 while (depth-- > 0 && partitions < count) {
174 if (partitions > count / 2)
return count;
180inline int ResolveParThreads(
int par_depth,
size_t count) {
181 size_t partitions = ResolveParPartitions(par_depth, count);
183 int threads = omp_get_max_threads();
187 return partitions < static_cast<size_t>(threads) ?
static_cast<int>(partitions) : threads;
192template <
typename Function>
193inline void ParallelFor(
int par_depth,
size_t count, Function function) {
194 size_t partitions = ResolveParPartitions(par_depth, count);
195 if (partitions <= 1) {
196 for (
size_t i = 0; i < count; ++i) function(i);
199 int threads = ResolveParThreads(par_depth, count);
200#pragma omp parallel for num_threads(threads)
201 for (
size_t partition = 0; partition < partitions; ++partition) {
202 size_t begin = partition * (count / partitions) + (partition < count % partitions ? partition : count % partitions);
203 size_t end = begin + count / partitions + (partition < count % partitions);
204 for (
size_t i = begin; i < end; ++i) function(i);
208template <
typename In>
209__host__ __device__
inline int4 Pack(In val) {
210 int4 buf = {0, 0, 0, 0};
211 if constexpr (
sizeof(In) <= 4) {
212 buf.x =
static_cast<int>(val);
213 }
else if constexpr (
sizeof(In) <= 8) {
214 auto v =
static_cast<uint64_t
>(val);
215 buf.x =
static_cast<int>(v & 0xFFFFFFFF);
216 buf.y =
static_cast<int>((v >> 32) & 0xFFFFFFFF);
218 auto v =
static_cast<__uint128_t
>(val);
219 buf.x =
static_cast<int>(v & 0xFFFFFFFF);
220 buf.y =
static_cast<int>((v >> 32) & 0xFFFFFFFF);
221 buf.z =
static_cast<int>((v >> 64) & 0xFFFFFFFF);
222 buf.w =
static_cast<int>((v >> 96) & 0xFFFFFFFF);
229namespace fss::detail {
232__host__ __device__
constexpr bool FitsElementCount(
size_t count) {
233 return count <= size_t(-1) /
sizeof(T);
236template <
typename T,
int bits>
237__host__ __device__
constexpr bool FitsFullDomain() {
238 if constexpr (bits < 0 || bits >=
sizeof(size_t) * 8) {
241 return FitsElementCount<T>(
size_t{1} << bits);
245template <
typename T,
int bits>
246__host__ __device__
constexpr size_t FullDomainSize() {
247 static_assert(FitsFullDomain<T, bits>(),
248 "full-domain element count and byte size must fit in size_t");
249 if constexpr (FitsFullDomain<T, bits>())
return size_t{1} << bits;
253template <
typename T,
int bits>
254__host__ __device__
constexpr bool FitsBinaryTree() {
255 if constexpr (!FitsFullDomain<T, bits>()) {
258 constexpr size_t leaves =
size_t{1} << bits;
259 constexpr size_t max_count = size_t(-1) /
sizeof(T);
260 return leaves - 1 <= max_count - leaves;
264template <
typename T,
int bits>
265__host__ __device__
constexpr size_t FullBinaryTreeSize() {
266 static_assert(FitsBinaryTree<T, bits>(),
267 "full binary-tree count and byte size must fit in size_t");
268 if constexpr (FitsBinaryTree<T, bits>()) {
269 constexpr size_t leaves =
size_t{1} << bits;
270 return leaves + (leaves - 1);
277namespace fss::gpu::detail {
282 explicit DeviceScratch(cudaStream_t stream) : stream_(stream) {}
283 DeviceScratch(
const DeviceScratch &) =
delete;
284 DeviceScratch &operator=(
const DeviceScratch &) =
delete;
285 ~DeviceScratch() { Release(); }
287 template <
typename T>
288 cudaError_t Allocate(
size_t count) {
289 if (count > std::numeric_limits<size_t>::max() /
sizeof(T))
return cudaErrorInvalidValue;
290 if (count == 0)
return cudaSuccess;
291 void *allocation =
nullptr;
292 cudaError_t status = cudaMallocAsync(&allocation, count *
sizeof(T), stream_);
293 if (status == cudaSuccess) data_ = allocation;
297 template <
typename T>
298 T *Get()
const {
return static_cast<T *
>(data_); }
300 cudaError_t Release() noexcept {
301 if (data_ ==
nullptr)
return cudaSuccess;
302 void *allocation = data_;
304 return cudaFreeAsync(allocation, stream_);
308 cudaStream_t stream_;
309 void *data_ =
nullptr;
312inline cudaError_t ReleaseScratch(cudaError_t status,
313 std::initializer_list<DeviceScratch *> buffers) {
314 for (
auto *buffer : buffers) {
315 cudaError_t release_status = buffer->Release();
316 if (status == cudaSuccess) status = release_status;
321inline void CheckCudaStatus(cudaError_t status) {
322 if (status != cudaSuccess)
323 throw std::runtime_error(std::string(
"cuda operation failed: ") + cudaGetErrorString(status));
326__device__ __host__
inline int4 Zero4() {
331__device__ __host__
inline bool GetBit(T x,
int i) {
332 return ((x >> i) & 1) != 0;
337template <
typename Query,
int bits>
338concept QueryFor = !std::is_same_v<Query, bool> &&
339 (std::is_unsigned_v<Query> || std::is_same_v<Query, __uint128_t>) &&
sizeof(Query) * 8 >= bits;
343namespace fss::detail {
351 bool prev_bit =
false;
353 __host__ __device__
void operator()(
bool t,
bool x_bit) {
354 if (x_bit != prev_bit) share ^= t;
360 __host__ __device__
bool PointLeaf(
bool t)
const {
361 return share != (!prev_bit && t);
367 template <
typename Scheme,
typename Hash>
368 static __host__ __device__
void HalfTreeExpandNode(Hash &hash, int4 instance_seed,
369 int4 node, int4 cw, int4 &left, int4 &right) {
370 Scheme::ExpandNode(hash, instance_seed, node, cw, left, right);
373 template <
typename Scheme,
typename Hash>
374 static __host__ __device__ int4 HalfTreePathStep(Hash &hash, int4 instance_seed,
375 int4 node, int4 cw,
bool take_right) {
376 return Scheme::PathStep(hash, instance_seed, node, cw, take_right);
379 template <
typename Scheme,
typename Hash>
380 static __host__ __device__ int4 PackedConvertLeaf(Hash &hash, int4 instance_seed,
381 int4 node, int4 cw) {
382 return Scheme::ConvertLeaf(hash, instance_seed, node, cw);
385 template <
typename Scheme,
typename Hash,
typename Cw>
386 static __host__ __device__
void HalfTreeConvertLeafPair(
bool party, Hash &hash,
387 int4 instance_seed, int4 node,
const typename Scheme::Cw &last_cw,
388 Cw &output_cw, int4 &left, int4 &right) {
389 Scheme::ConvertLeafPair(party, hash, instance_seed, node, last_cw,
390 output_cw, left, right);
393 template <
typename Scheme,
typename Prg,
typename Group,
typename Cw>
394 static __host__ __device__
void DcfExpandNode(Prg &prg, int4 state,
const Group &value,
395 bool party,
const Cw &cw, int4 &left, Group &left_value,
396 int4 &right, Group &right_value) {
397 Scheme::ExpandNode(prg, state, value, party, cw,
398 left, left_value, right, right_value);
401 template <
typename Scheme,
typename Prg,
typename Group,
typename Cw>
402 static __host__ __device__
void DcfPathStep(Prg &prg, int4 state,
const Group &value,
403 bool party,
const Cw &cw,
bool take_right,
404 int4 &next_state, Group &next_value) {
405 Scheme::PathStep(prg, state, value, party, cw,
406 take_right, next_state, next_value);
409 template <
typename Scheme,
typename Prg>
410 static __host__ __device__
void DpfExpandNode(Prg &prg, int4 state, int4 cw,
411 bool right_control, int4 &left, int4 &right) {
412 Scheme::ExpandNode(prg, state, cw, right_control, left, right);
415 template <
typename Scheme,
typename Prg>
416 static __host__ __device__ int4 DpfPathStep(Prg &prg, int4 state, int4 cw,
417 bool right_control,
bool take_right) {
418 return Scheme::PathStep(prg, state, cw, right_control, take_right);
421 template <
typename Scheme>
422 static __host__ __device__ int4 DpfConvertLeaf(
bool party, int4 state, int4 cw) {
423 return Scheme::ConvertLeaf(party, state, cw);
426 template <
typename Scheme>
427 static __host__ __device__
auto &GetHash(Scheme &scheme) {
return scheme.hash_; }
429 template <
typename Scheme>
430 static __host__ __device__
const auto &GetPrg(
const Scheme &scheme) {
return scheme.prg_; }
432 template <
typename Scheme>
433 static __host__ __device__
const auto &GetHash(
const Scheme &scheme) {
return scheme.hash_; }
435 template <
typename Scheme>
436 static __host__ __device__
const auto &GetXorHash(
const Scheme &scheme) {
return scheme.xor_hash_; }
438 template <
typename Scheme>
439 static __host__ __device__
const auto &GetPrp(
const Scheme &scheme) {
return scheme.prp_; }
441 template <
typename Scheme>
442 static __host__ __device__
auto GetInstanceSeed(
const Scheme &scheme) {
return scheme.instance_seed_; }
447 template <
typename Scheme>
448 static __host__ __device__
void DpfGenCw(Scheme &scheme,
typename Scheme::Cw *cws,
449 const int4 *s0s,
auto a, int4 b_buf) {
450 scheme.Generate(
typename Scheme::AosWriter{cws}, s0s, a, b_buf);
453 template <
typename Scheme>
454 static __host__ __device__ int4 DpfEvalCw(Scheme &scheme,
bool b, int4 s0,
455 const typename Scheme::Cw *cws,
auto x) {
456 return scheme.EvalCws(b, s0, cws, x);
459 template <
typename Scheme>
460 static void DpfEvalAllCw(Scheme &scheme,
bool b, int4 s0,
461 const typename Scheme::Cw *cws, int4 *ys,
int par_depth) {
462 scheme.EvalAllCws(b, s0, cws, ys, par_depth);
465 template <
int frontier_depth = 16,
int threads_per_block = 256,
typename Scheme>
466 static __host__ cudaError_t DpfEvalAllGpuBatchCw(Scheme &scheme,
bool b,
467 cuda::std::span<const int4> s0s, cuda::std::span<const typename Scheme::Cw> cws,
468 cuda::std::span<int4> ys, cudaStream_t stream) {
469 return scheme.template EvalAllGpuBatchCw<frontier_depth, threads_per_block>(
470 b, s0s, cws, ys, stream);
473 template <
typename Scheme>
474 static __host__ __device__
int VdpfGenCw(Scheme &scheme,
typename Scheme::Cw *cws,
475 cuda::std::span<int4, 4> cs, cuda::std::span<const int4, 2> s0s,
auto a, int4 b_buf) {
476 return scheme.Generate(
typename Scheme::AosWriter{cws}, cs, s0s.data(), a, b_buf);
479 template <
typename Scheme>
480 static __host__ __device__
auto VdpfEvalCw(Scheme &scheme,
bool b, int4 s0,
481 cuda::std::span<const typename Scheme::Cw> cws, cuda::std::span<const int4, 4> cs,
483 return scheme.EvalCws(b, s0, cws.data(), cs, x, y);
486 template <
typename Scheme>
487 static __host__ __device__
auto MakeInner(Scheme &scheme) {
488 if constexpr (
requires {
typename Scheme::InnerVdpf; }) {
489 return typename Scheme::InnerVdpf{scheme.prg_, scheme.xor_hash_, scheme.hash_};
491 return typename Scheme::InnerDpf{scheme.prg_};
495 template <
typename Scheme,
typename Block,
typename In>
496 static __host__ __device__
bool HalfTreeGrottoLeafParity(Block block, In x) {
497 return Scheme::LeafParity(block, x);
bool GetLsb(int4 val)
Return bit 0 of the fourth word of val.
Definition util.cuh:58
void RequireArg(bool ok, const char *msg)
Check a caller precondition.
Definition util.cuh:73
int4 SetLsb(int4 val, bool bit)
Return val with bit 0 of its fourth word set to bit.
Definition util.cuh:51
int4 Xor(int4 lhs, int4 rhs)
Return the bitwise XOR of two 128-bit blocks.
Definition util.cuh:29