@@ -123,13 +123,18 @@ struct Scratch {
123123 }
124124};
125125
126- template <typename OffT>
126+ // Arrow/Vortex variable-length view records are 16 bytes. Values up to 12
127+ // bytes are stored inline after the u32 length. Longer values store their
128+ // first four bytes, backing-buffer index, and byte offset.
129+ constexpr uint32_t MAX_INLINED_SIZE = 12 ;
130+
131+ template <typename CodeOffsetT, typename OutputOffsetT>
127132struct FSSTArgs {
128133 // Compressed FSST code stream, contiguous across all strings. String
129134 // `sid`'s codes live in `[codes_offsets[sid], codes_offsets[sid + 1])`.
130135 const uint8_t *__restrict codes_bytes;
131136 // Per-string offsets into `codes_bytes`, length `num_strings + 1`.
132- const OffT *__restrict codes_offsets;
137+ const CodeOffsetT *__restrict codes_offsets;
133138 // FSST symbol table.
134139 const uint64_t *__restrict symbols;
135140 // Length in bytes (1..=8) of each entry in `symbols`. The remaining bits
@@ -138,19 +143,55 @@ struct FSSTArgs {
138143 // Buffer to write decoded data into.
139144 uint8_t *__restrict output_bytes;
140145 // Per-string offsets into `output_bytes`, length `num_strings + 1`.
141- const uint64_t *__restrict output_offsets;
146+ const OutputOffsetT *__restrict output_offsets;
142147 // Validity of each string.
143148 const uint8_t *__restrict validity_bits;
149+ // Optional output views, one 16-byte uint4 per string. A null pointer
150+ // requests bytes-only decoding for heaps that need the host rollover path.
151+ uint4 *__restrict output_views;
144152};
145153
146- template <typename OffT>
147- __device__ inline void fsst_decode_string (const FSSTArgs<OffT> &args, uint64_t sid) {
154+ // Build one BinaryView from the bytes this thread just decoded. The Rust
155+ // caller only provides output_views when every offset fits in the view's u32
156+ // fields and the decoded heap is exposed as backing buffer zero.
157+ template <typename CodeOffsetT, typename OutputOffsetT>
158+ __device__ inline void fsst_write_view (const FSSTArgs<CodeOffsetT, OutputOffsetT> &args, uint64_t sid) {
159+ if (args.output_views == nullptr ) {
160+ return ;
161+ }
162+
163+ const uint64_t start = args.output_offsets [sid];
164+ const uint32_t len = (uint32_t )(args.output_offsets [sid + 1 ] - start);
165+ if (len <= MAX_INLINED_SIZE ) {
166+ uint32_t words[3 ] = {0 , 0 , 0 };
167+ #pragma unroll
168+ for (uint32_t i = 0 ; i < MAX_INLINED_SIZE ; i++) {
169+ if (i < len) {
170+ words[i >> 2 ] |= (uint32_t )args.output_bytes [start + i] << (8u * (i & 3u ));
171+ }
172+ }
173+ args.output_views [sid] = make_uint4 (len, words[0 ], words[1 ], words[2 ]);
174+ return ;
175+ }
176+
177+ const uint32_t prefix = (uint32_t )args.output_bytes [start] |
178+ ((uint32_t )args.output_bytes [start + 1 ] << 8u ) |
179+ ((uint32_t )args.output_bytes [start + 2 ] << 16u ) |
180+ ((uint32_t )args.output_bytes [start + 3 ] << 24u );
181+ args.output_views [sid] = make_uint4 (len, prefix, 0 , (uint32_t )start);
182+ }
183+
184+ template <typename CodeOffsetT, typename OutputOffsetT>
185+ __device__ inline void fsst_decode_string (const FSSTArgs<CodeOffsetT, OutputOffsetT> &args, uint64_t sid) {
148186 if (((args.validity_bits [sid >> 3 ] >> (sid & 7u )) & 1u ) == 0u ) {
187+ if (args.output_views != nullptr ) {
188+ args.output_views [sid] = make_uint4 (0 , 0 , 0 , 0 );
189+ }
149190 return ;
150191 }
151192
152- OffT in_pos = args.codes_offsets [sid];
153- const OffT in_end = args.codes_offsets [sid + 1 ];
193+ CodeOffsetT in_pos = args.codes_offsets [sid];
194+ const CodeOffsetT in_end = args.codes_offsets [sid + 1 ];
154195 uint64_t out_pos = args.output_offsets [sid];
155196 const uint64_t out_end = args.output_offsets [sid + 1 ];
156197
@@ -181,46 +222,66 @@ __device__ inline void fsst_decode_string(const FSSTArgs<OffT> &args, uint64_t s
181222 sym &= mask;
182223
183224 scratch.push (sym, len);
184- in_pos += (OffT )consumed;
225+ in_pos += (CodeOffsetT )consumed;
185226 }
186227
187228 // Epilogue: drain everything that's left.
188229 while (scratch.cursor > 0 ) {
189230 scratch.drain (args.output_bytes , out_pos, out_end);
190231 }
232+
233+ fsst_write_view (args, sid);
191234}
192235
193- #define GENERATE_FSST_KERNEL (suffix, OffT ) \
236+ #define FSST_GRID_STRIDE_LOOP (CodeOffsetT, OutputOffsetT, args ) \
237+ const uint64_t elements_per_block = (uint64_t )blockDim .x * ELEMENTS_PER_THREAD ; \
238+ const uint64_t block_start = (uint64_t )blockIdx .x * elements_per_block; \
239+ const uint64_t block_end = (block_start + elements_per_block < num_strings) \
240+ ? (block_start + elements_per_block) \
241+ : num_strings; \
242+ for (uint64_t sid = block_start + threadIdx .x; sid < block_end; sid += blockDim .x) { \
243+ fsst_decode_string<CodeOffsetT, OutputOffsetT>(args, sid); \
244+ }
245+
246+ #define GENERATE_FSST_VIEW_KERNEL (suffix, CodeOffsetT ) \
194247 extern " C" __global__ void fsst_##suffix(const uint8_t *__restrict codes_bytes, \
195- const OffT *__restrict codes_offsets, \
248+ const CodeOffsetT *__restrict codes_offsets, \
196249 const uint64_t *__restrict symbols, \
197250 const uint8_t *__restrict symbol_lengths, \
198251 const uint64_t *__restrict output_offsets, \
199252 const uint8_t *__restrict validity_bits, \
200253 uint8_t *__restrict output_bytes, \
254+ uint4 *__restrict output_views, \
201255 uint64_t num_strings) { \
202- const FSSTArgs<OffT> args = { \
203- codes_bytes, \
204- codes_offsets, \
205- symbols, \
206- symbol_lengths, \
207- output_bytes, \
208- output_offsets, \
209- validity_bits, \
256+ const FSSTArgs<CodeOffsetT, uint64_t > args = { \
257+ codes_bytes, codes_offsets, symbols, symbol_lengths, output_bytes, output_offsets, \
258+ validity_bits, output_views, \
210259 }; \
211- \
212- const uint64_t elements_per_block = (uint64_t )blockDim .x * ELEMENTS_PER_THREAD ; \
213- const uint64_t block_start = (uint64_t )blockIdx .x * elements_per_block; \
214- const uint64_t block_end = (block_start + elements_per_block < num_strings) \
215- ? (block_start + elements_per_block) \
216- : num_strings; \
217- \
218- for (uint64_t sid = block_start + threadIdx .x ; sid < block_end; sid += blockDim .x ) { \
219- fsst_decode_string<OffT>(args, sid); \
220- } \
260+ FSST_GRID_STRIDE_LOOP (CodeOffsetT, uint64_t , args) \
221261 }
222262
223- GENERATE_FSST_KERNEL (u8 , uint8_t )
224- GENERATE_FSST_KERNEL (u16 , uint16_t )
225- GENERATE_FSST_KERNEL (u32 , uint32_t )
226- GENERATE_FSST_KERNEL (u64 , uint64_t )
263+ #define GENERATE_FSST_VARBIN_KERNEL (suffix, CodeOffsetT ) \
264+ extern " C" __global__ void fsst_varbin_##suffix(const uint8_t *__restrict codes_bytes, \
265+ const CodeOffsetT *__restrict codes_offsets, \
266+ const uint64_t *__restrict symbols, \
267+ const uint8_t *__restrict symbol_lengths, \
268+ const int32_t *__restrict output_offsets, \
269+ const uint8_t *__restrict validity_bits, \
270+ uint8_t *__restrict output_bytes, \
271+ uint64_t num_strings) { \
272+ const FSSTArgs<CodeOffsetT, int32_t > args = { \
273+ codes_bytes, codes_offsets, symbols, symbol_lengths, output_bytes, output_offsets, \
274+ validity_bits, nullptr , \
275+ }; \
276+ FSST_GRID_STRIDE_LOOP (CodeOffsetT, int32_t , args) \
277+ }
278+
279+ GENERATE_FSST_VIEW_KERNEL (u8 , uint8_t )
280+ GENERATE_FSST_VIEW_KERNEL (u16 , uint16_t )
281+ GENERATE_FSST_VIEW_KERNEL (u32 , uint32_t )
282+ GENERATE_FSST_VIEW_KERNEL (u64 , uint64_t )
283+
284+ GENERATE_FSST_VARBIN_KERNEL (u8 , uint8_t )
285+ GENERATE_FSST_VARBIN_KERNEL (u16 , uint16_t )
286+ GENERATE_FSST_VARBIN_KERNEL (u32 , uint32_t )
287+ GENERATE_FSST_VARBIN_KERNEL (u64 , uint64_t )
0 commit comments