@@ -130,6 +130,8 @@ CUDF_KERNEL void offset_bitmask_binop(Binop op,
130130 * @tparam Binop Type of binary operator
131131 *
132132 * @param op The binary operator to apply to the bitmasks
133+ * @param identity Identity element of `op`, used to seed each segment's reduction; a
134+ * segment containing no masks therefore produces a destination filled with `identity`
133135 * @param num_segments Number of segments to process
134136 * @param destinations Array of pointers to destination bitmasks where results will be written
135137 * @param destination_size Size of each destination mask in bitmask words (not bits)
@@ -142,6 +144,7 @@ CUDF_KERNEL void offset_bitmask_binop(Binop op,
142144 */
143145template <typename Binop>
144146CUDF_KERNEL void segmented_offset_bitmask_binop (Binop op,
147+ bitmask_type identity,
145148 size_type num_segments,
146149 bitmask_type** const destinations,
147150 size_type destination_size,
@@ -162,14 +165,15 @@ CUDF_KERNEL void segmented_offset_bitmask_binop(Binop op,
162165 auto const lane = warp.thread_rank ();
163166
164167 // Process one segment per warp.
165- auto const segment_id = cudf::detail::grid_1d::global_thread_id () / warp.size ();
168+ auto const segment_id = cudf::detail::grid_1d::global_thread_id () / warp.size ();
169+
170+ // Exit early if this warp doesn't have a valid segment, before indexing anything by `segment_id`
171+ if (segment_id >= num_segments) { return ; }
172+
166173 auto const segment_start = segment_offsets[segment_id];
167174 auto const segment_end = segment_offsets[segment_id + 1 ];
168175 auto const destination = destinations[segment_id];
169176
170- // Exit early if this warp doesn't have a valid segment
171- if (segment_id >= num_segments) { return ; }
172-
173177 // Calculate bit range information
174178 auto const last_bit_index = source_size_bits - 1 ;
175179 auto const last_word_index = cudf::word_index (last_bit_index);
@@ -181,15 +185,12 @@ CUDF_KERNEL void segmented_offset_bitmask_binop(Binop op,
181185 // Process the mask such that each thread in warp handles different words
182186 for (size_type destination_word_index = lane; destination_word_index < destination_size;
183187 destination_word_index += warp.size ()) {
184- // Get the first mask word
185- bitmask_type destination_word =
186- detail::get_mask_offset_word (sources[segment_start],
187- destination_word_index,
188- source_begin_bits[segment_start],
189- source_begin_bits[segment_start] + source_size_bits);
188+ // Seeding with the identity keeps an empty segment well defined and avoids indexing `sources`
189+ // with a segment's start offset before knowing that the segment contains anything.
190+ bitmask_type destination_word = identity;
190191
191192 // Apply the binary operation with each source mask in the segment
192- for (size_type mask_pos = segment_start + 1 ; mask_pos < segment_end; mask_pos++) {
193+ for (size_type mask_pos = segment_start; mask_pos < segment_end; mask_pos++) {
193194 destination_word =
194195 op (destination_word,
195196 detail::get_mask_offset_word (sources[mask_pos],
@@ -242,7 +243,8 @@ rmm::device_uvector<size_type> inplace_segmented_bitmask_binop(
242243 size_type mask_size_bits,
243244 host_span<size_type const > segment_offsets,
244245 cuda::stream_ref stream,
245- rmm::device_async_resource_ref mr);
246+ rmm::device_async_resource_ref mr,
247+ bitmask_type identity = ~bitmask_type{0 });
246248
247249/* *
248250 * @copydoc bitmask_binop(Binop op, host_span<bitmask_type const* const>, host_span<size_type>
@@ -280,7 +282,8 @@ segmented_bitmask_binop(Binop op,
280282 size_type mask_size_bits,
281283 host_span<size_type const > segment_offsets,
282284 cuda::stream_ref stream,
283- rmm::device_async_resource_ref mr)
285+ rmm::device_async_resource_ref mr,
286+ bitmask_type identity = ~bitmask_type{0 })
284287{
285288 auto const num_bytes = bitmask_allocation_size_bytes (mask_size_bits);
286289 CUDF_EXPECTS (
@@ -314,7 +317,8 @@ segmented_bitmask_binop(Binop op,
314317 mask_size_bits,
315318 segment_offsets,
316319 stream,
317- cudf::get_current_device_resource_ref ());
320+ cudf::get_current_device_resource_ref (),
321+ identity);
318322
319323 return std::pair (std::move (h_destination_masks),
320324 cudf::detail::make_std_vector<size_type>(null_counts, stream));
@@ -376,9 +380,12 @@ size_type inplace_bitmask_binop(Binop op,
376380 * @param[in] masks Host span of pointers to source bitmasks to be operated on
377381 * @param[in] masks_begin_bits The bit offsets from which each source mask is to be operated on
378382 * @param[in] mask_size_bits The number of bits to be operated on in each mask
379- * @param[in] segment_offsets Host span of offsets defining the segments for the operation
383+ * @param[in] segment_offsets Host span of offsets defining the segments for the operation; must be
384+ * non-decreasing. An empty segment writes `identity` to its destination mask
380385 * @param[in] stream CUDA stream used for device memory operations and kernel launches
381386 * @param[in] mr Device memory resource used to allocate output device vector of null counts
387+ * @param[in] identity Identity element of `op`, defaulted to the all-set mask that is the identity
388+ * of bitwise AND
382389 * @return A device vector containing the counts of unset bits in the destination mask corresponding
383390 * to each segment after the AND operation
384391 */
@@ -392,7 +399,8 @@ rmm::device_uvector<size_type> inplace_segmented_bitmask_binop(
392399 size_type mask_size_bits,
393400 host_span<size_type const > segment_offsets,
394401 cuda::stream_ref stream,
395- rmm::device_async_resource_ref mr)
402+ rmm::device_async_resource_ref mr,
403+ bitmask_type identity)
396404{
397405 CUDF_EXPECTS (
398406 std::all_of (masks_begin_bits.begin (), masks_begin_bits.end (), [](auto b) { return b >= 0 ; }),
@@ -402,8 +410,14 @@ rmm::device_uvector<size_type> inplace_segmented_bitmask_binop(
402410 " Mask pointer cannot be null" );
403411 CUDF_EXPECTS (segment_offsets.size () >= 2 ,
404412 " At least one segment needs to be passed for bitwise operations" );
413+ CUDF_EXPECTS (std::is_sorted (segment_offsets.begin (), segment_offsets.end ()),
414+ " Segment offsets must be non-decreasing" );
415+ CUDF_EXPECTS (
416+ segment_offsets.front () >= 0 && segment_offsets.back () <= static_cast <size_type>(masks.size ()),
417+ " Segment offsets are out of the bounds of the mask array" );
405418
406- rmm::device_uvector<size_type> d_null_counts (segment_offsets.size () - 1 , stream, mr);
419+ auto const num_segments = static_cast <size_type>(segment_offsets.size () - 1 );
420+ rmm::device_uvector<size_type> d_null_counts (num_segments, stream, mr);
407421 auto temp_mr = cudf::get_current_device_resource_ref ();
408422 auto d_masks = cudf::detail::make_device_uvector_async (masks, stream, temp_mr);
409423 auto d_begin_bits = cudf::detail::make_device_uvector_async (masks_begin_bits, stream, temp_mr);
@@ -413,13 +427,13 @@ rmm::device_uvector<size_type> inplace_segmented_bitmask_binop(
413427 auto constexpr block_size = 256 ;
414428 auto constexpr warps_per_block =
415429 util::div_rounding_up_safe<int >(block_size, cudf::detail::warp_size);
416- auto const num_blocks =
417- util::div_rounding_up_safe<int >(segment_offsets.size () - 1 , warps_per_block);
430+ auto const num_blocks = util::div_rounding_up_safe<int >(num_segments, warps_per_block);
418431 static_assert (block_size % cudf::detail::warp_size == 0 ,
419432 " For segmented bitmask operations, block size must be a multiple of warp size" );
420433 segmented_offset_bitmask_binop<<<num_blocks, block_size, 0 , stream.get()>>> (
421434 op,
422- segment_offsets.size () - 1 ,
435+ identity,
436+ num_segments,
423437 dest_masks.data (),
424438 dest_mask_size,
425439 d_masks.data (),
0 commit comments