myl7/fss 2.0.0
Function secret sharing (FSS) primitives including distributed point/comparison function (DPF/DCF)
Loading...
Searching...
No Matches
util.cuh
Go to the documentation of this file.
1// SPDX-License-Identifier: Apache-2.0
7
8#pragma once
9#include <cuda_runtime.h>
10#include <cuda/std/array>
11#include <cuda/std/span>
12#ifdef _OPENMP
13#include <omp.h>
14#endif
15#include <cstddef>
16#include <cstdint>
17#include <climits>
18#include <cassert>
19
20#include <initializer_list>
21#include <limits>
22#include <stdexcept>
23#include <string>
24#include <type_traits>
25
26namespace fss::util {
27
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};
31}
32
41template <size_t N>
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]);
47 return out;
48}
49
51__host__ __device__ inline int4 SetLsb(int4 val, bool bit) {
52 if (bit) val.w |= 1;
53 else val.w &= ~1;
54 return val;
55}
56
58__host__ __device__ inline bool GetLsb(int4 val) {
59 return (val.w & 1) != 0;
60}
61
73__host__ __device__ inline void RequireArg(bool ok, const char *msg) {
74 assert(ok && msg);
75}
76
77} // namespace fss::util
78
79// -----------------------------------------------------------------------------
80
81namespace fss::detail {
82
83// Check subtraction before adding one, including a 128-bit full-domain range.
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;
89 }
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;
95}
96
97
98// Subdivide [first, last] into aligned chunks and compute the launch geometry
99// shared by the range GPU launchers: per-chunk frontier, thread width, block
100// root depth, and block count. frontier_cap reserves trailing levels that the
101// caller's kernel resolves outside the block frontier (HalfTree uses one).
102// launch(chunk_first, depth, offset, frontier, thread_bits, root, blocks)
103// starts each chunk's kernels; its return value propagates.
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]");
112 size_t count;
113 if (!RangeOutputCount<in_bits>(first, last, ys_count, count)) return cudaErrorInvalidValue;
114 constexpr int kThreadBits = [] {
115 int bits = 0;
116 for (int threads = threads_per_block; threads > 1; threads >>= 1) ++bits;
117 return bits;
118 }();
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;
123 return depth;
124 }();
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);
135 });
136}
137
138// Visit aligned power-of-two chunks in input order without shifting by In's width.
139// A caller may cap depth to split chunks further for its kernel's fixed limits.
140template <int in_bits, typename In, typename Callback>
141__host__ inline cudaError_t ForEachAlignedRange(
142 In first, In last, int max_depth, Callback callback) {
143 size_t offset = 0;
144 for (;;) {
145 int depth = 0;
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;
151 ++depth;
152 }
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);
158 offset += count;
159 }
160}
161
162// Resolve a tree split depth without shifting beyond a machine word.
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;
166}
167
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;
175 partitions *= 2;
176 }
177 return partitions;
178}
179
180inline int ResolveParThreads(int par_depth, size_t count) {
181 size_t partitions = ResolveParPartitions(par_depth, count);
182#ifdef _OPENMP
183 int threads = omp_get_max_threads();
184#else
185 int threads = 1;
186#endif
187 return partitions < static_cast<size_t>(threads) ? static_cast<int>(partitions) : threads;
188}
189
190// Each scheduled partition owns a contiguous range. A depth of zero never
191// enters an OpenMP region, including when called from a shallow fallback path.
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);
197 return;
198 }
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);
205 }
206}
207
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);
217 } else {
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);
223 }
224 return buf;
225}
226
227} // namespace fss::detail
228
229namespace fss::detail {
230
231template <typename T>
232__host__ __device__ constexpr bool FitsElementCount(size_t count) {
233 return count <= size_t(-1) / sizeof(T);
234}
235
236template <typename T, int bits>
237__host__ __device__ constexpr bool FitsFullDomain() {
238 if constexpr (bits < 0 || bits >= sizeof(size_t) * 8) {
239 return false;
240 } else {
241 return FitsElementCount<T>(size_t{1} << bits);
242 }
243}
244
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;
250 return 0;
251}
252
253template <typename T, int bits>
254__host__ __device__ constexpr bool FitsBinaryTree() {
255 if constexpr (!FitsFullDomain<T, bits>()) {
256 return false;
257 } else {
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;
261 }
262}
263
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);
271 }
272 return 0;
273}
274
275} // namespace fss::detail
276
277namespace fss::gpu::detail {
278
279// Per-call temporary storage. Release is queued after its uses on the same stream.
280class DeviceScratch {
281public:
282 explicit DeviceScratch(cudaStream_t stream) : stream_(stream) {}
283 DeviceScratch(const DeviceScratch &) = delete;
284 DeviceScratch &operator=(const DeviceScratch &) = delete;
285 ~DeviceScratch() { Release(); }
286
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;
294 return status;
295 }
296
297 template <typename T>
298 T *Get() const { return static_cast<T *>(data_); }
299
300 cudaError_t Release() noexcept {
301 if (data_ == nullptr) return cudaSuccess;
302 void *allocation = data_;
303 data_ = nullptr;
304 return cudaFreeAsync(allocation, stream_);
305 }
306
307private:
308 cudaStream_t stream_;
309 void *data_ = nullptr;
310};
311
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;
317 }
318 return status;
319}
320
321inline void CheckCudaStatus(cudaError_t status) {
322 if (status != cudaSuccess)
323 throw std::runtime_error(std::string("cuda operation failed: ") + cudaGetErrorString(status));
324}
325
326__device__ __host__ inline int4 Zero4() {
327 return {0, 0, 0, 0};
328}
329
330template <typename T>
331__device__ __host__ inline bool GetBit(T x, int i) {
332 return ((x >> i) & 1) != 0;
333}
334
335// Constrains a GPU query element type to an unsigned integer wide enough for
336// the scheme's input domain.
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;
340
341} // namespace fss::gpu::detail
342
343namespace fss::detail {
344
345// State of the prefix-parity algorithm (Section 4 of the paper) on x's path. Called with the
346// control bit t of every expanded node and the branch x_bit taken from it, share accumulates
347// the control bits of the nodes where the path turns, taking the edge into the root as a left
348// edge. GrottoDcf and HalfTreeGrottoDcf share it and differ only in the leaf step.
349struct GrottoTurns {
350 bool share = false;
351 bool prev_bit = false;
352
353 __host__ __device__ void operator()(bool t, bool x_bit) {
354 if (x_bit != prev_bit) share ^= t;
355 prev_bit = x_bit;
356 }
357
358 // Leaf step when each leaf is a single input: XOR the leaf's control bit if the path ends
359 // with a left edge.
360 __host__ __device__ bool PointLeaf(bool t) const {
361 return share != (!prev_bit && t);
362 }
363};
364
365// Temporary access for existing CUDA helpers; retire entries with their consumers.
366struct SchemeAccess {
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);
371 }
372
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);
377 }
378
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);
383 }
384
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);
391 }
392
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);
399 }
400
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);
407 }
408
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);
413 }
414
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);
419 }
420
421 template <typename Scheme>
422 static __host__ __device__ int4 DpfConvertLeaf(bool party, int4 state, int4 cw) {
423 return Scheme::ConvertLeaf(party, state, cw);
424 }
425
426 template <typename Scheme>
427 static __host__ __device__ auto &GetHash(Scheme &scheme) { return scheme.hash_; }
428
429 template <typename Scheme>
430 static __host__ __device__ const auto &GetPrg(const Scheme &scheme) { return scheme.prg_; }
431
432 template <typename Scheme>
433 static __host__ __device__ const auto &GetHash(const Scheme &scheme) { return scheme.hash_; }
434
435 template <typename Scheme>
436 static __host__ __device__ const auto &GetXorHash(const Scheme &scheme) { return scheme.xor_hash_; }
437
438 template <typename Scheme>
439 static __host__ __device__ const auto &GetPrp(const Scheme &scheme) { return scheme.prp_; }
440
441 template <typename Scheme>
442 static __host__ __device__ auto GetInstanceSeed(const Scheme &scheme) { return scheme.instance_seed_; }
443
444 // Cw-layout correction-word entry points for library owners that keep the Cw
445 // storage contract (DMPF/VDMPF bucket keys). Public callers use the component
446 // strided APIs; these reuse the schemes' private shared implementations.
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);
451 }
452
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);
457 }
458
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);
463 }
464
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);
471 }
472
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);
477 }
478
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,
482 auto x, int4 &y) {
483 return scheme.EvalCws(b, s0, cws.data(), cs, x, y);
484 }
485
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_};
490 } else {
491 return typename Scheme::InnerDpf{scheme.prg_};
492 }
493 }
494
495 template <typename Scheme, typename Block, typename In>
496 static __host__ __device__ bool HalfTreeGrottoLeafParity(Block block, In x) {
497 return Scheme::LeafParity(block, x);
498 }
499};
500
501} // namespace fss::detail
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