@@ -117,12 +117,8 @@ __shared__ uint64_t runend_cursors[BLOCK_SIZE];
117117// ═══════════════════════════════════════════════════════════════════════════
118118
119119// / Apply one scalar operation to N values in registers.
120- // /
121- // / `abs_pos` is the absolute output position of the first value to process.
122- // / It is used by scalar operations that apply patches, e.g. ALP.
123120template <typename T, uint32_t N>
124- __device__ inline void
125- scalar_op (T *values, const struct ScalarOp &op, char *__restrict smem, uint64_t abs_pos = 0 ) {
121+ __device__ inline void scalar_op (T *values, const struct ScalarOp &op, char *__restrict smem) {
126122 switch (op.op_code ) {
127123 case ScalarOp::FOR : {
128124 const T ref = static_cast <T>(op.params .frame_of_ref .reference );
@@ -165,30 +161,9 @@ scalar_op(T *values, const struct ScalarOp &op, char *__restrict smem, uint64_t
165161 values[i] = static_cast <T>(__double_as_longlong (r));
166162 }
167163 }
168- // Apply ALP patches: override positions whose float value couldn't
169- // be reconstructed through the ALP encode/decode cycle.
170- // Per-value cursor — with a slice offset, a tile's N values can
171- // straddle two FL chunks, so each value needs its own lookup.
172- if (op.params .alp .patches_ptr != 0 ) {
173- const auto &patches = *reinterpret_cast <const GPUPatches *>(op.params .alp .patches_ptr );
174- const uint32_t chunk_start = patches.offset / FL_CHUNK ;
175- #pragma unroll
176- for (uint32_t i = 0 ; i < N; ++i) {
177- uint64_t my_pos = (N > 1 ) ? abs_pos + i * blockDim .x + threadIdx .x : abs_pos;
178- uint64_t orig = my_pos + patches.offset ;
179- uint32_t chunk = static_cast <uint32_t >(orig / FL_CHUNK ) - chunk_start;
180- uint32_t within = static_cast <uint32_t >(orig % FL_CHUNK );
181- PatchesCursor<T> cursor (patches, chunk, 0 , 1 );
182- auto patch = cursor.next ();
183- while (patch.index != FL_CHUNK ) {
184- if (patch.index == within) {
185- values[i] = patch.value ;
186- break ;
187- }
188- patch = cursor.next ();
189- }
190- }
191- }
164+ // ALP patches are scattered cooperatively after the stage has decoded.
165+ // Looking up patches here would restart a chunk cursor for every value,
166+ // turning sparse exception handling into O(values * patches).
192167 break ;
193168 }
194169 case ScalarOp::DICT : {
@@ -225,6 +200,58 @@ scatter_patches_chunk(const GPUPatches &patches, T *__restrict out, uint32_t chu
225200 }
226201}
227202
203+ // / Scatter patches overlapping a logical output range.
204+ // /
205+ // / `logical_start` and `range_len` are relative to the sliced array represented by
206+ // / `patches`. Patch indices are stored in the coordinate space of the original array,
207+ // / so add `patches.offset` while locating chunks and subtract it when addressing `out`.
208+ // / Every thread cooperates on each overlapping FastLanes chunk.
209+ template <typename T>
210+ __device__ inline void scatter_patches_range (const GPUPatches &patches,
211+ T *__restrict out,
212+ uint64_t logical_start,
213+ uint32_t range_len) {
214+ if (range_len == 0 ) {
215+ return ;
216+ }
217+
218+ const uint64_t original_start = logical_start + patches.offset ;
219+ const uint64_t original_end = original_start + range_len;
220+ const uint32_t patch_chunk_start = patches.offset / FL_CHUNK ;
221+ const uint32_t first_chunk = static_cast <uint32_t >(original_start / FL_CHUNK );
222+ const uint32_t last_chunk = static_cast <uint32_t >((original_end - 1 ) / FL_CHUNK );
223+
224+ for (uint32_t original_chunk = first_chunk; original_chunk <= last_chunk; ++original_chunk) {
225+ const uint32_t chunk = original_chunk - patch_chunk_start;
226+ PatchesCursor<T> cursor (patches, chunk, threadIdx .x , blockDim .x );
227+ auto patch = cursor.next ();
228+ while (patch.index != FL_CHUNK ) {
229+ const uint64_t original_pos = static_cast <uint64_t >(original_chunk) * FL_CHUNK + patch.index ;
230+ if (original_pos >= original_start && original_pos < original_end) {
231+ out[original_pos - patches.offset ] = patch.value ;
232+ }
233+ patch = cursor.next ();
234+ }
235+ }
236+ }
237+
238+ // / Apply patch payloads attached to scalar operations after their stage has decoded.
239+ template <typename T>
240+ __device__ inline void
241+ scatter_scalar_patches (const Stage &stage, T *__restrict out, uint64_t logical_start, uint32_t range_len) {
242+ for (uint8_t op_idx = 0 ; op_idx < stage.num_scalar_ops ; ++op_idx) {
243+ const auto &op = stage.scalar_ops [op_idx];
244+ if (op.op_code == ScalarOp::ALP && op.params .alp .patches_ptr != 0 ) {
245+ const auto &patches = *reinterpret_cast <const GPUPatches *>(op.params .alp .patches_ptr );
246+ // All ordinary writes must complete before exception values overwrite them.
247+ __syncthreads ();
248+ scatter_patches_range<T>(patches, out, logical_start, range_len);
249+ // A later stage may consume this patched shared-memory output.
250+ __syncthreads ();
251+ }
252+ }
253+ }
254+
228255// ═══════════════════════════════════════════════════════════════════════════
229256// Source ops
230257// ═══════════════════════════════════════════════════════════════════════════
@@ -491,7 +518,7 @@ __device__ void execute_output_stage(T *__restrict output,
491518 smem);
492519
493520 for (uint8_t op = 0 ; op < stage.num_scalar_ops ; ++op) {
494- scalar_op<T, VALUES_PER_TILE >(values, stage.scalar_ops [op], smem, tile_start );
521+ scalar_op<T, VALUES_PER_TILE >(values, stage.scalar_ops [op], smem);
495522 }
496523
497524#pragma unroll
@@ -513,7 +540,7 @@ __device__ void execute_output_stage(T *__restrict output,
513540 source_op<T, 1 >(&val, src, raw_input, ptype, smem_src, i, gpos, smem);
514541
515542 for (uint8_t op = 0 ; op < stage.num_scalar_ops ; ++op) {
516- scalar_op<T, 1 >(&val, stage.scalar_ops [op], smem, gpos );
543+ scalar_op<T, 1 >(&val, stage.scalar_ops [op], smem);
517544 }
518545 __stcs (&output[gpos], val);
519546 }
@@ -525,6 +552,8 @@ __device__ void execute_output_stage(T *__restrict output,
525552 }
526553 elem_idx += chunk_len;
527554 }
555+
556+ scatter_scalar_patches<T>(stage, output, block_start, block_len);
528557}
529558
530559// ═══════════════════════════════════════════════════════════════════════════
@@ -559,7 +588,7 @@ __device__ void execute_input_stage(const Stage &stage, char *__restrict smem) {
559588 for (uint32_t elem_idx = threadIdx .x ; elem_idx < stage.len ; elem_idx += blockDim .x ) {
560589 T val = smem_out[elem_idx];
561590 for (uint8_t op = 0 ; op < stage.num_scalar_ops ; ++op) {
562- scalar_op<T, 1 >(&val, stage.scalar_ops [op], smem, elem_idx );
591+ scalar_op<T, 1 >(&val, stage.scalar_ops [op], smem);
563592 }
564593 smem_out[elem_idx] = val;
565594 }
@@ -583,14 +612,16 @@ __device__ void execute_input_stage(const Stage &stage, char *__restrict smem) {
583612 T val;
584613 source_op<T, 1 >(&val, src, raw_input, stage.source_ptype , nullptr , 0 , elem_idx, smem);
585614 for (uint8_t op = 0 ; op < stage.num_scalar_ops ; ++op) {
586- scalar_op<T, 1 >(&val, stage.scalar_ops [op], smem, elem_idx );
615+ scalar_op<T, 1 >(&val, stage.scalar_ops [op], smem);
587616 }
588617 smem_out[elem_idx] = val;
589618 }
590619 // Write barrier: smem region is fully populated for subsequent
591620 // stages to read.
592621 __syncthreads ();
593622 }
623+
624+ scatter_scalar_patches<T>(stage, smem_out, 0 , stage.len );
594625}
595626
596627// ═══════════════════════════════════════════════════════════════════════════
0 commit comments