cuSBF
Loading...
Searching...
No Matches
fastx_buffer_reader.hpp
Go to the documentation of this file.
1#pragma once
2
3#include <bit>
4#include <cstdint>
5#include <optional>
6#include <string>
7#include <string_view>
8#include <utility>
9
10#if defined(__aarch64__) || defined(_M_ARM64)
11 #include <arm_neon.h>
12#endif
13#if defined(__ARM_FEATURE_SVE2)
14 #include <arm_sve.h>
15#elif defined(__x86_64__) || defined(_M_X64)
16 #include <immintrin.h>
17#endif
18
19#include <cusbf/error.hpp>
20#include <cusbf/Fastx.hpp>
21
22namespace cusbf::detail {
23
24[[nodiscard]] inline size_t fastx_line_end_scalar(std::string_view data, size_t position) {
25 while (position < data.size() && data[position] != '\n' && data[position] != '\r') {
26 ++position;
27 }
28 return position;
29}
30
31#if (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__) && !defined(__CUDACC__)
32[[gnu::target("avx2")]] [[nodiscard]] inline size_t
33fastx_line_end_avx2(std::string_view data, size_t position) {
34 const __m256i newline = _mm256_set1_epi8('\n');
36 while (data.size() - position >= 32) {
37 const __m256i bytes =
38 _mm256_loadu_si256(reinterpret_cast<const __m256i*>(data.data() + position));
39 const auto mask = static_cast<uint32_t>(_mm256_movemask_epi8(_mm256_or_si256(
41 )));
42 if (mask != 0) {
43 return position + static_cast<size_t>(__builtin_ctz(mask));
44 }
45 position += 32;
46 }
47 return fastx_line_end_scalar(data, position);
48}
49#endif
50
51#if defined(__ARM_FEATURE_SVE)
52[[nodiscard]] inline size_t fastx_line_end_sve(std::string_view data, size_t position) {
53 while (position < data.size()) {
54 const svbool_t active = svwhilelt_b8(position, data.size());
55 const svuint8_t bytes =
56 svld1_u8(active, reinterpret_cast<const uint8_t*>(data.data() + position));
57 const svbool_t matches =
61 }
62 position += svcntb();
63 }
64 return position;
65}
66#endif
67
68#if defined(__aarch64__) || defined(_M_ARM64)
69[[nodiscard]] inline size_t fastx_line_end_neon(std::string_view data, size_t position) {
70 const uint8x16_t newline = vdupq_n_u8('\n');
72 while (data.size() - position >= 16) {
73 const uint8x16_t bytes = vld1q_u8(reinterpret_cast<const uint8_t*>(data.data() + position));
74 const uint8x16_t matches =
77 const uint64_t low = vgetq_lane_u64(words, 0);
78 if (low != 0) {
79 return position + (std::countr_zero(low) >> 3);
80 }
81 const uint64_t high = vgetq_lane_u64(words, 1);
82 if (high != 0) {
83 return position + 8 + (std::countr_zero(high) >> 3);
84 }
85 position += 16;
86 }
87 return fastx_line_end_scalar(data, position);
88}
89#endif
90
92
94#if defined(__ARM_FEATURE_SVE)
95 return fastx_line_end_sve;
96#elif defined(__aarch64__) || defined(_M_ARM64)
98#elif (defined(__GNUC__) || defined(__clang__)) && defined(__x86_64__) && !defined(__CUDACC__)
100#else
102#endif
103}
104
105static const fastx_line_end_fn line_end = resolve_fastx_line_end();
106
107[[nodiscard]] inline size_t fastx_line_end(std::string_view data, size_t position) {
108 return line_end(data, position);
109}
110
113 public:
120 explicit FastxBufferReader(std::string_view data, std::string_view source_name = "<buffer>")
121 : data_(data), source_name_(source_name) {}
122
130 record.header.clear();
131 record.sequence.clear();
132
133 const auto header = readHeaderLine();
134 if (!header) {
135 return Err(header.error());
136 }
137 if (header->empty()) {
138 return false;
139 }
140
141 const char header_tag = header->front();
142 if (format_ == FastxFormat::unknown) {
143 if (header_tag == '>') {
144 format_ = FastxFormat::fasta;
145 } else if (header_tag == '@') {
146 format_ = FastxFormat::fastq;
147 } else {
148 return Err(
149 parseError("expected FASTA or FASTQ header", fastx_column_at(*header, 0))
150 );
151 }
152 }
153
154 const char expected_header = format_ == FastxFormat::fasta ? '>' : '@';
156 return Err(parseError(
157 "mixed FASTA and FASTQ records are not supported", fastx_column_at(*header, 0)
158 ));
159 }
160
161 record.header.assign(header->substr(1));
162 if (format_ == FastxFormat::fasta) {
163 CUSBF_TRY(readFastaSequence(record.sequence));
164 } else {
165 CUSBF_TRY(readFastqSequence(record.sequence));
166 }
167 return true;
168 }
169
171 [[nodiscard]] std::string_view buffer() const noexcept {
172 return data_;
173 }
174
188 appendNextRecord(FastxRecord& record, std::string& sequence, std::string_view& buffer) {
189 record.header.clear();
190 record.sequence.clear();
191
192 const auto header = readHeaderLine();
193 if (!header) {
194 return Err(header.error());
195 }
196 if (header->empty()) {
197 return std::nullopt;
198 }
199
200 const char header_tag = header->front();
201 if (format_ == FastxFormat::unknown) {
202 if (header_tag == '>') {
203 format_ = FastxFormat::fasta;
204 } else if (header_tag == '@') {
205 format_ = FastxFormat::fastq;
206 } else {
207 return Err(
208 parseError("expected FASTA or FASTQ header", fastx_column_at(*header, 0))
209 );
210 }
211 }
212
213 const char expected_header = format_ == FastxFormat::fasta ? '>' : '@';
215 return Err(parseError(
216 "mixed FASTA and FASTQ records are not supported", fastx_column_at(*header, 0)
217 ));
218 }
219
220 record.header.assign(header->substr(1));
221 if (format_ == FastxFormat::fasta) {
222 const auto sequence_offset = static_cast<uint64_t>(position_);
223 const std::string_view line = readLine();
224 if (line.empty()) {
225 return Err(parseError("FASTA record missing sequence", fastx_column_at(line, 0)));
226 }
227 if (!line.empty() && line.front() == '>') {
228 return Err(parseError("FASTA record missing sequence", fastx_column_at(line, 0)));
229 }
230
231 if (position_ < data_.size() && data_[position_] != '>') {
232 const auto owned_offset = static_cast<uint64_t>(sequence.size());
233 sequence.append(line.data(), line.size());
234 CUSBF_TRY(readFastaSequence(sequence));
235 return RecordRange{
237 static_cast<uint64_t>(sequence.size()) - owned_offset,
238 };
239 }
240
241 if (buffer.empty()) {
242 buffer = data_;
243 }
244 return RecordRange{sequence_offset, static_cast<uint64_t>(line.size())};
245 }
246
247 const auto sequence_offset = static_cast<uint64_t>(sequence.size());
248 CUSBF_TRY(readFastqSequence(sequence, sequence.size()));
249 return RecordRange{
251 static_cast<uint64_t>(sequence.size()) - sequence_offset,
252 };
253 }
254
255 private:
256 std::string_view data_;
257 std::string_view source_name_;
258 size_t position_{0};
260 uint64_t line_number_{};
261
262 [[nodiscard]] Error parseError(std::string_view message, uint32_t column) const {
263 return Error::fastx_parse(
264 SourceLocation::fastx(source_name_, static_cast<uint32_t>(line_number_), column),
265 message
266 );
267 }
268
269 [[nodiscard]] std::string_view readLine() {
270 if (position_ >= data_.size()) {
271 return {};
272 }
273
274 const size_t end = fastx_line_end(data_, position_);
275
276 const std::string_view line = data_.substr(position_, end - position_);
277 position_ = end;
278 if (position_ < data_.size() && data_[position_] == '\r') {
279 ++position_;
280 }
281 if (position_ < data_.size() && data_[position_] == '\n') {
282 ++position_;
283 }
284 ++line_number_;
285 return line;
286 }
287
288 [[nodiscard]] Result<std::string_view> readHeaderLine() {
289 while (position_ < data_.size()) {
290 const std::string_view line = readLine();
291 if (!line.empty()) {
292 return line;
293 }
294 }
295 return std::string_view{};
296 }
297
298 [[nodiscard]] Result<void> readFastaSequence(std::string& sequence) {
299 while (position_ < data_.size()) {
300 if (data_[position_] == '>') {
301 return {};
302 }
303 const std::string_view line = readLine();
304 sequence.append(line.data(), line.size());
305 }
306 return {};
307 }
308
309 [[nodiscard]] Result<void>
310 readFastqSequence(std::string& sequence, uint64_t sequence_offset = 0) {
311 std::string_view last_line;
312 while (position_ < data_.size()) {
313 const std::string_view line = readLine();
314 last_line = line;
315 if (!line.empty() && line.front() == '+') {
316 CUSBF_TRY(readFastqQualities(sequence.size() - sequence_offset));
317 return {};
318 }
319 sequence.append(line.data(), line.size());
320 }
321 return Err(parseError(
322 "unterminated FASTQ record: missing '+' separator",
323 fastx_column_at(last_line, last_line.size() > 0 ? last_line.size() - 1 : 0)
324 ));
325 }
326
327 [[nodiscard]] Result<void> readFastqQualities(uint64_t expected_length) {
329 std::string_view last_line;
330 while (quality_length < expected_length && position_ < data_.size()) {
331 const std::string_view line = readLine();
332 last_line = line;
333 quality_length += line.size();
335 return Err(parseError(
336 "FASTQ quality length exceeds sequence length",
338 ));
339 }
340 }
342 return Err(parseError(
343 "FASTQ quality length does not match sequence length",
345 ));
346 }
347 return {};
348 }
349};
350
351} // namespace cusbf::detail
FASTA/FASTQ parser over a contiguous in-memory buffer.
FastxBufferReader(std::string_view data, std::string_view source_name="<buffer>")
Constructs a reader over a contiguous in-memory FASTA/FASTQ buffer.
Result< bool > nextRecord(FastxRecord &record)
Reads the next record into record.
Result< std::optional< RecordRange > > appendNextRecord(FastxRecord &record, std::string &sequence, std::string_view &buffer)
Parses one record with optional zero-copy sequence views for single-line FASTA.
std::string_view buffer() const noexcept
Entire mmap or owned buffer backing this reader.
#define CUSBF_TRY(expr)
Propagates a cusbf::Result failure from the enclosing function (GNU statement expression).
Definition error.hpp:246
size_t fastx_line_end_scalar(std::string_view data, size_t position)
uint32_t fastx_column_at(std::string_view line, size_t byte_index)
1-based column at byte_index within line (clamped to the line end).
Definition Fastx.hpp:268
uint32_t fastx_quality_short_column(std::string_view line)
1-based column where a quality run ends too short (position after the last byte).
Definition Fastx.hpp:287
uint32_t fastx_quality_excess_column(uint64_t quality_length, uint64_t expected_length, std::string_view line)
1-based column of the first quality byte that exceeds expected_length.
Definition Fastx.hpp:276
FastxFormat
Detected file format for a FASTA/FASTQ stream.
Definition Fastx.hpp:243
@ fasta
FASTA (> headers).
@ fastq
FASTQ (@ headers).
@ unknown
Format not yet determined from the first header.
size_t fastx_line_end(std::string_view data, size_t position)
consteval bool separatorPositionAlwaysEncodesInvalid(char *input, uint64_t separatorPosition, uint64_t index)
Recursively tests whether placing the separator byte at any position in an input of valid bytes alway...
Definition Alphabet.cuh:37
decltype(&fastx_line_end_scalar) fastx_line_end_fn
fastx_line_end_fn resolve_fastx_line_end()
cuda::std::unexpected< Error > Err(Error error)
Failure return; converts to any Result<T> via cuda::std::unexpected.
Definition error.hpp:219
static Error fastx_parse(SourceLocation site, std::string_view detail)
Definition error.hpp:123
Ordered non-overlapping byte range for one record inside a dense sequence batch.
Definition Fastx.hpp:24
Fallible API result: cuda::std::expected<T, Error> with cuSBF factories.
Definition error.hpp:152
static SourceLocation fastx(std::string_view file, uint32_t line, uint32_t column)
Definition error.hpp:34
A single sequence record extracted from a FASTA/FASTQ stream.
Definition Fastx.hpp:253